Part 7 · 2 chapters · ~12 min
Password Storage
Why passwords need slow hashes, PBKDF2, bcrypt, scrypt and Argon2id with measured costs, salts and peppers, parameters and upgrading hashes on login, breached-password checks, and why passkeys remove the problem.
13
Slow on purpose
code
// Argon2id with sensible parameters (argon2 package); verify handles the salt and parameters stored in the hash
import argon2 from 'argon2';
const hash = await argon2.hash(password, { type: argon2.argon2id, memoryCost: 19456, timeCost: 2, parallelism: 1 });
// $argon2id$v=19$m=19456,t=2,p=1$<salt>$<hash>
const ok = await argon2.verify(hash, attempt);
// upgrade old hashes transparently on successful login
if (ok && argon2.needsRehash(hash, currentParams)) await saveHash(user, await argon2.hash(attempt, currentParams));PASSWORD HASHING IS SLOW ON PURPOSE
milliseconds per hash on this machine
swipe the figure sideways, or tap expand for full screen
1/4
why slow
Stolen password databases are attacked offline with GPUs trying billions of guesses per second against fast hashes. A deliberately slow hash makes each guess cost milliseconds instead of nanoseconds.
fast hashes: billions of guesses per secondSHA-256 is wrong for passwords
14
Beyond hashing
password hygiene
- Check new passwords against breached-password lists (k-anonymity range APIs such as Have I Been Pwned).
- Rate-limit and add friction to login attempts per account and per IP range.
- Never log passwords, and redact them in error tracking.
- Run hashing off the event loop (Node: it already uses the threadpool; size it, Node course part 2).
- Prefer passkeys (Auth part 3): nothing to steal from your database that works anywhere else.