Easier to read with cream paper, a tint, more contrast or larger text? Choose your colours and text, once, for every page.

Free lesson

Storing passwords and keys safely

After this lesson: I can store passwords and keys safely.

60 min lesson8 commandsEveryday Linux commands lab: the thirty commands engineers type from memory

A customer table that leaks

Password hashing means storing a slow, salted hash of each password instead of the password itself. If a thief copies the database, they get hashes that are very costly to turn back into passwords.

A shop's customer table leaks. What the thief can do next depends on one choice the developers made months earlier.

By the end of this lesson

Note

I can store passwords and keys safely.

Key words

WordWhat it means
Salta random value added to each password before hashing
Slow hasha hash made costly on purpose, to slow down guessing
Work factorhow costly the hash is, such as the number of rounds
Dictionary attacktrying a list of likely passwords
Key rotationreplacing a key with a new one on a plan
Secret storea locked place that hands keys to approved programs only

The cells students fill in are left blank in this preview.

A plain password hash is easy to crack

Never store a password as it is typed. Storing its plain hash is little better. People choose common passwords, and one password always gives one hash.

An attacker hashes a list of likely passwords and compares. This is a dictionary attack. A fast hash like SHA-256 lets a graphics card try billions of guesses a second.

Hash the password hunter2 with no salt

Command
printf 'hunter2' | sha256sum

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

Find the password behind a stolen hash

Code: python
import hashlib
# A stolen hash of an invented password. No salt was used.
stolen = "f52fbd32b2b3b86ff88ef6c490628285f482af15ddcb29541f94bcf526a3f6c7"
guesses = ["password", "123456", "letmein"]
# Add the guess "hunter2" to the list, then press Run.
for guess in guesses:
    if hashlib.sha256(guess.encode()).hexdigest() == stolen:
        print("the password was", guess)

A salt makes every hash different

A salt is a random value stored beside the hash. It is added to the password before hashing. Two users with the same password then get different hashes.

The salt is not secret. It forces the attacker to attack each account on its own, so a ready-made list of hashes is useless.

Hash hunter2 with the salt k3q9

Command
printf 'k3q9hunter2' | sha256sum

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

Hash hunter2 with the salt z8m1

Command
printf 'z8m1hunter2' | sha256sum

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

Worked example: one password, three records

Stored asHash (first 12 characters)
No salt, hunter2f52fbd32b2b3
Salt k3q9, then hunter224f39aa1267f
Salt z8m1, then hunter20c458853fecc

The cells students fill in are left blank in this preview.

A common mistake

Warning

Many people think encrypting passwords is safer than hashing them. Encrypted passwords can be decrypted by anyone who steals the key. A site never needs to read a password back, only to check one. So hash it, and never encrypt it.

Password hashes must be slow on purpose

A login can afford a tenth of a second. An attacker needs billions of guesses. A slow hash makes each guess costly, so the attack becomes too expensive.

Use Argon2id first. scrypt and bcrypt are sound too. PBKDF2 is acceptable with at least 600,000 rounds. Store the settings with the hash, so you can raise them later. Use a vetted library.

Worked example: a record that keeps its settings

PartValueWhy it is kept
Schemepbkdf2Tells you which hash to run
Rounds1000 (a demo: far too few)Lets you raise it for new logins
Saltk3q9Different for each account
Hash957dfc979f9a1524fe1f...What the attempt is compared with

The cells students fill in are left blank in this preview.

A slow salted hash, 1000 rounds (a demo)

Command
python3 -c "import hashlib; print(hashlib.pbkdf2_hmac('sha256', b'hunter2', b'k3q9', 1000).hex())"

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

Keys belong in a secret store

A key that leaks is as bad as a password that leaks. Make keys with a secure random source, such as Python's secrets. Never type one up, and never use random.

Keep a key out of code and out of email. Give it to programs from a secret store. Allow only the programs that need it, rotate it on a plan, and replace it at once if it may have leaked.

Worked example: key sizes to choose today

KeySize to use
AES key128 bits, or 256 bits for long-lived data
HMAC key256 bits
RSA key2048 bits at least, 3072 for long-lived data
Elliptic curve keyAbout 256 bits (X25519, Ed25519)
Key from a passwordMade with Argon2id or scrypt, never a plain hash

The cells students fill in are left blank in this preview.

Make a pretend key file

Command
printf 'not a real key' > demo.key

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

Allow only you to read and write it

Command
chmod 600 demo.key

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

Check the file's mode as a number

Command
stat -c '%a' demo.key

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

Make a random key, count its bytes

Command
python3 -c "import secrets; print(len(secrets.token_bytes(32)))"

Shown here, not run. Create a free account to press Run and type it into your own lab’s terminal.

About this lab and its source: Everyday Linux commands lab: the thirty commands engineers type from memory

Teaching a class? Set this module for your students with a teacher guide, marked assignments and a lab for every student.