Basic

Why the salt matters

Two users with the same password had the same hash. A salt makes each hash unique, so a stolen table cannot be cracked by looking up common hashes.

What you will see

True True

About 1 min of reading

Try It

See that the same password stores as different hashes.

1

Hash the same password twice. The digests differ because each salt is random.

import bcrypt a = bcrypt.hashpw(b'hunter2', bcrypt.gensalt()) b = bcrypt.hashpw(b'hunter2', bcrypt.gensalt()) print(a == b)

Before you run it, say out loud what it will print. Then open the answer.

What it prints
False
2

Both still verify the password, because each digest carries its own salt.

import bcrypt print(bcrypt.checkpw(b'hunter2', a)) print(bcrypt.checkpw(b'hunter2', b))

Before you run it, say out loud what it will print. Then open the answer.

What it prints
True True

Put it in order

Before you write anything, put these lines back in the right order. Click a line to move it up one step. This support step is easier than writing code from scratch, and it is here to make the last example stick.

Data Flow

A random per-password salt is mixed in before hashing, making each digest unique so a precomputed lookup table no longer matches, and the salt is stored inside the digest.
1 Random salt per password
↓
2 Mixed in before hashing
↓
3 Each digest is unique
↓
4 Lookup table cannot match
Line by line

Read the first example one line at a time. A line that prints shows what appears on screen; the rest set things up quietly.

import bcrypt sets nothing on screen
a = bcrypt.hashpw(b'hunter2', bcrypt.gensalt()) sets nothing on screen
b = bcrypt.hashpw(b'hunter2', bcrypt.gensalt()) sets nothing on screen
print(a == b) False
Why this works
Salt defeats lookup

A salt makes each user's hash unique, so a thief cannot hash a list of common passwords once and match every account. The salt is stored in the digest, not hidden. Its purpose is to stop bulk reversal, not to be secret.

Per-user, not global

A fresh random salt per password is right. A single global salt still lets one hash match many accounts. bcrypt generates the salt for you, so you rarely write it; the mistake is reaching for a fast hash instead.

Examples

No salt, same hash

sha256('hunter2') == sha256('hunter2')

Without a salt, one password equals one hash everywhere. A thief can precompute a table of common passwords and reverse them in bulk.

Salt makes it unique

bcrypt.hash('hunter2')  # different digest each call

bcrypt mixes in a random salt, so the same password stores differently every time. A rainbow table no longer matches.

Salt is stored with it

$2b$12$N4x...  <- the salt is inside the digest

The salt is not a secret, it is stored in the hash string. Its job is uniqueness, not secrecy, so it travels in the clear.

Words this lesson uses

Each one is defined here on this site. Open the entry for the full definition and the other words it points at.

← Never store a password plain
Previous Lesson
Never store a password plain
Next Lesson
Keeping the key secret
Keeping the key secret →