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
SHA-256 (never for passwords)~1 µsPBKDF2-SHA256, 600k iterations79 msscrypt N=2^15, r=866 msArgon2id (recommended)tune to ~50-500 ms
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
  1. Check new passwords against breached-password lists (k-anonymity range APIs such as Have I Been Pwned).
  2. Rate-limit and add friction to login attempts per account and per IP range.
  3. Never log passwords, and redact them in error tracking.
  4. Run hashing off the event loop (Node: it already uses the threadpool; size it, Node course part 2).
  5. Prefer passkeys (Auth part 3): nothing to steal from your database that works anywhere else.