BitBootcampBitBootcamp ← all explainers 🔄 Review ⚙ Settings

CYBER-04-002-P · Primer — Password Hashing & Salting: Storing Secrets You Can Verify but Not Recover

The idea in one sentence

A password store should let you confirm the right password without ever holding the password itself, and the trick is to save a slow, one-way, per-user fingerprint instead of the secret.

Why it exists

Every login system faces the same awkward problem: to check a password, it needs something on file to compare against. The naive answer — write the password down in the database — is a disaster waiting to happen. Databases get leaked, backed up to insecure places, read by curious admins, and dumped by attackers who find a single SQL injection bug. The moment a plaintext password table leaks, every account is instantly compromised, and worse, because people reuse passwords, their accounts on other sites are compromised too.

So we need a way to store a value that proves "yes, this is the password" on login, but that is useless to a thief who steals the file. That is the entire job of password hashing. A hash is a one-way function: it turns any input into a fixed-size fingerprint, and there is no reverse button. You can compute hash("hunter2") in a millisecond, but given only the fingerprint you cannot compute the word back. On login, you hash what the user typed and compare fingerprints. If they match, the password was right — and you never had to keep the password around to find out.

But naive hashing alone is not enough, and attackers taught us exactly why. Rainbow tables are giant precomputed dictionaries mapping common passwords to their hashes; if everyone's password123 hashes to the same value, one lookup cracks thousands of accounts. Credential stuffing takes leaked username/password pairs from one breach and replays them against other sites. And modern GPUs can compute billions of fast hashes per second, so a plain SHA-256 of a password is guessed almost as fast as it is stored. The defenses in this primer — salts and slow, adaptive hashing — exist precisely to break each of those attacks.

How it works

Three ingredients, layered on top of each other, turn a fragile fingerprint into a robust one.

One-way hashing is the base. A hash function is deterministic (same input, same output) and irreversible (no output-to-input path). That gives us the verify-without-storing property.

Salting defeats precomputation. A salt is a unique, random value generated per user and stored alongside the hash (it does not need to be secret). You hash the salt and the password together. Now two users who both chose password123 get completely different fingerprints, because their salts differ. That single change kills rainbow tables — an attacker cannot precompute anything, because they would need a separate table for every possible salt. It also means cracking one account tells you nothing about the next.

Adaptive KDFs (key derivation functions) make guessing slow on purpose. Fast hashes are the enemy here: you want your one login check to be cheap enough, but an attacker's billions of offline guesses to be ruinously expensive. Adaptive functions have a tunable work factor you crank up as hardware improves:

A pepper is an optional extra layer: a single secret value, shared across all users, mixed into the hash but stored outside the database (in an app config, HSM, or secrets manager). If only the database leaks, the pepper the attacker is missing makes the stolen hashes far harder to crack.

The one gotcha

Do not reach for the fast, general-purpose hashes you already know — MD5, SHA-1, SHA-256 — to store passwords. They are excellent tools for file integrity, where speed is a feature, but for passwords their speed is exactly the vulnerability: a GPU chews through billions of SHA-256 guesses per second. Password storage wants a function that is deliberately slow and tunable, so use a purpose-built adaptive KDF (Argon2id, bcrypt, or scrypt; PBKDF2 only when FIPS compliance forces it). Equally, never invent your own scheme or reuse one salt for everyone — let a vetted library generate a fresh random salt per user and pick sane work factors for you.

See it — verify without storing the password

flowchart TD
    A["User submits<br/>password at login"] --> B["Fetch this user's<br/>stored salt + hash"]
    B --> C["Compute KDF<br/>salt + password<br/>(bcrypt / Argon2id)"]
    C --> D{"New hash equals<br/>stored hash?"}
    D -->|Yes| E["Grant access<br/>(password never stored)"]
    D -->|No| F["Reject login"]
    G["Attacker steals<br/>the hash table"] --> H["No reverse function<br/>+ unique salts<br/>+ slow work factor"]
    H --> I["Offline guessing<br/>is ruinously slow"]

The one-line takeaway

Store a salted, slow, one-way fingerprint — never the password — so you can verify a login but a stolen database yields nothing worth cracking.

Where you'll meet it

You will meet this the first time you build a signup-and-login flow and have to decide what goes in the password column — the answer is a bcrypt or Argon2id hash, produced by a library, never the raw string. You will meet it again during security reviews and audits, where "are passwords stored with a salted adaptive hash?" is a baseline question, and in incident response, where the difference between "we leaked plaintext" and "we leaked Argon2id hashes" is the difference between a catastrophe and a manageable event. It also underpins why sites nudge you toward unique passwords and MFA: hashing protects the store, but credential stuffing still works if you reuse a password that leaked in plaintext somewhere else.

Named concepts: one-way hashing, salts and peppers, adaptive KDFs (bcrypt, Argon2), credential stuffing & rainbow tables