Password Storage Security: Argon2id, Bcrypt, Salting & NIST Guidelines
The authoritative production guide to modern password security: Why fast hashes (MD5, SHA-256) are fatal for password storage, how Argon2id and Bcrypt defeat GPU attacks, tuning work factors, and NIST SP 800-63B rules.
1. Fast Hashes vs Slow Hashes: The Fundamental Law
| Hash Algorithm | Design Goal | GPU Hash Rate (RTX 4090, approx.) | Password Suitability | Copy |
|---|---|---|---|---|
| MD5 | Legacy checksum | ~160 billion/sec | Catastrophic: broken and trivially brute-forced | |
| SHA-256 | Data integrity & signatures | ~22 billion/sec | Unacceptable for passwords: far too fast | |
| Bcrypt (Cost 12) | Password storage (CPU-hard) | ~1,400/sec (halves with each +1 cost) | Strong; slow by design (72-byte password limit) | |
| Argon2id | Memory-hard KDF | Depends on memory and time parameters; far lower than fast hashes | Recommended default (winner of the Password Hashing Competition) |
2. The Mechanics of Salting: Defeating Rainbow Tables
// Example Bcrypt Hash:
// $2b$12$e8Yh9TqZ8p1m3Kj7Lk9We.3oRjV6qY2N8zK1pT9sF2wE4rY6uI0oO
// [ $2b$ ] - Algorithm identifier (Bcrypt 2b format)
// [ 12$ ] - Cost factor (2^12 = 4,096 key expansion iterations)
// [ e8Yh9TqZ8p1m3Kj7Lk9We. ] - 22 chars (16 bytes Base64-encoded random salt)
// [ 3oRjV6qY2N8zK1pT9sF2wE4rY6uI0oO ] - 31 chars (184-bit Blowfish ciphertext hash)The salt is stored directly inside the hash output string; the verification function extracts the salt automatically!
3. Bcrypt vs Argon2id: Memory Hardness
/* Recommended OWASP Parameter Baseline for Argon2id:
* Memory Cost (m) : 65,536 KiB (64 MB of RAM)
* Time Cost (t) : 3 iterations
* Parallelism (p) : 4 threads
* Target Latency : ~500ms on server hardware
*/
import argon2 from "argon2";
// Hash password
const hash = await argon2.hash(userPassword, {
type: argon2.argon2id,
memoryCost: 65536,
timeCost: 3,
parallelism: 4
});
// Verify password
const isValid = await argon2.verify(hash, candidatePassword);Argon2id combines Argon2d (resistance to GPU cracking) and Argon2i (resistance to side-channel cache attacks).
4. Timing Attacks & Constant-Time Comparisons
import crypto from "crypto";
// ANTI-PATTERN: Leaks character matches via variable-time comparison
// if (receivedToken === storedToken) { ... }
// PRODUCTION PATTERN: Constant-time comparison immune to timing side-channels
function safeTokenCompare(a: string, b: string): boolean {
const bufA = Buffer.from(a);
const bufB = Buffer.from(b);
if (bufA.length !== bufB.length) return false;
return crypto.timingSafeEqual(bufA, bufB);
}crypto.timingSafeEqual runs in deterministic execution time regardless of where string mismatches occur.
5. NIST SP 800-63B Modern Password Policy Standards
| Legacy Practice (Deprecated by NIST) | Modern NIST SP 800-63B Standard | Rationale | Copy |
|---|---|---|---|
| Arbitrary Periodic Expiry (90 days) | Do NOT force expiration without evidence of breach | Forces users to pick predictable patterns (Spring2026!) | |
| Complex Character Rules (!@#$1aA) | Allow minimum 12-15 characters of any UTF-8 text | Length beats complexity; allow spaces in passphrases | |
| Truncating Long Passwords | Allow passwords up to at least 64 characters | Permits password manager generated passphrases | |
| Security Knowledge Questions | Forbidden (Mother maiden name, favorite pet) | Easily researched via social engineering and OSINT |
Related Free Online Tools
Put these techniques into practice immediately with our privacy-first browser utilities:
Generate secure Bcrypt password hashes with configurable salt rounds and verify candidate passwords against existing hashes.
Generate cryptographically random passwords with high entropy and customizable character sets.
Inspect fast cryptographic SHA-256 hashes and understand why fast hashes are unsuitable for password storage.