Key Derivation
PBKDF2 → scrypt → Argon2: making passwords expensive.
Key Derivation
Humans type passwords. Cryptography needs keys. A key derivation function (KDF) bridges the two: turn a low-entropy password into a high-entropy symmetric key, and do it in a way that makes brute-force attacks expensive.
The same machinery is also used for password storage — a database entry is just "the KDF output of the user's password, alongside a per-user salt and the KDF parameters".
Analogy
Imagine an old-fashioned hand-cranked butter churn that takes a small splash of cream and, after ten thousand slow turns of the crank, yields a block of butter of a very specific weight. The churn is deliberately slow — there's no shortcut, no assembly line of child churners can speed a single batch up, because each turn feeds off the last. If someone steals your recipe book and wants to try a million different cream sources to find yours, they must sit at a churn for every single attempt. A KDF does the same thing for passwords: it takes the short, weak cream of what the user typed and laboriously churns out a long, strong key that anyone trying to reverse-engineer will spend years doing by hand.
What a KDF does (and why a plain hash is wrong)
A plain hash like SHA-256 is fast: billions of guesses per second on a GPU. Store SHA256(password) in your user table and an attacker with a leak can try every password in a dictionary in seconds.
A password KDF is deliberately slow and deliberately memory-hungry so the attacker's parallel hardware stops paying off. You tune the cost so a legitimate login takes ~100ms — attackers doing 10⁹ guesses pay 10⁸ seconds of compute, uphill.
Every modern KDF also takes a salt: a random per-user value. Two users with the same password still get different outputs, so rainbow tables are useless and an attacker who breaks one hash hasn't broken them all.
The three you should know
PBKDF2 (RFC 2898 / NIST SP 800-132)
- HMAC-based. Only tunable knob is iterations.
- FIPS-approved, available everywhere — Web Crypto, Node, Python, Java, every browser.
- Not memory-hard: a GPU attacker parallelises trivially.
- 2024 OWASP minimum: 600,000 iterations with HMAC-SHA-256.
Use PBKDF2 when you have to — regulatory requirements, deeply constrained platforms.
scrypt (Percival, 2009)
- Deliberately memory-hard. Tune
N(CPU/memory cost),r(block size),p(parallelism). - Good resistance to GPU/ASIC attacks — memory is expensive.
- Slightly harder to tune than Argon2; the three parameters interact.
- Used by cryptocurrency wallets and some password managers.
Argon2 (Password Hashing Competition winner, RFC 9106)
- Three variants: Argon2d (fast, GPU-resistant, vulnerable to side channels), Argon2i (side-channel resistant, slower), Argon2id (hybrid — use this one).
- Tune memory (
m), time (t), parallelism (p), output length. - Current OWASP first-choice.
- 2024 default: Argon2id with
m=19456KiB,t=2,p=1.
Salts, pepper, and format
- Salt: random, ≥ 16 bytes, stored alongside the hash. Never a secret.
- Pepper (optional): a server-wide secret XORed in, stored outside the database. Raises the bar if the DB leaks but the config doesn't.
- Encoded format: use the library's standard encoding (e.g.
$argon2id$v=19$m=19456,t=2,p=1$salt$hash). It embeds the parameters so you can upgrade costs over time.
Deriving encryption keys
KDFs aren't only for password storage. To derive an AES-GCM key from a user-typed passphrase, run the same KDF (Argon2id or PBKDF2), request 32 bytes of output, and use those bytes as the key.
The salt should be stored with the ciphertext. The IV/nonce is separate and comes from a CSPRNG.
Picking parameters: the 100ms rule
Target: one legitimate login should take ~100ms on your server. Measure on the actual hardware, not on your dev laptop. Write the parameters to the stored hash so upgrades don't break old entries. Recompute and re-store when the user logs in, if the parameters you'd use now are stronger than what's saved.
Takeaways
- Never store or compare raw or plain-hashed passwords.
- Always salt. Never pepper without a salt.
- Argon2id is the first-choice. scrypt second. PBKDF2 only when forced.
- KDFs derive encryption keys too — same rules: per-ciphertext salt, known parameters.