bcrypt vs Argon2: Password Hashing in 2026
Choose the right password hashing algorithm and tune its parameters for production.
Storing passwords as plain text is unthinkable. Storing them with MD5 or SHA-256 is barely better. The right answer is a slow, memory-hard hash.
Why Slow on Purpose
GPU farms hash billions of SHA-256 candidates per second. A slow hash like bcrypt or Argon2 caps that to thousands per second per dollar of hardware. The factor of a million matters during a breach.
bcrypt: The Reliable Veteran
import bcrypt from 'bcrypt';
const hash = await bcrypt.hash(password, 12);
const ok = await bcrypt.compare(password, hash);
The cost factor (12 in 2026) doubles work for each increment. Re-tune every couple of years as hardware improves. bcrypt is GPU-resistant but not memory-hard.
Limitation: 72-byte input cap. Pre-hash with SHA-256 if users might have long passphrases.
Argon2: The Modern Winner
Argon2id won the 2015 Password Hashing Competition and is now OWASP's recommended default.
import argon2 from 'argon2';
const hash = await argon2.hash(password, {
type: argon2.argon2id,
memoryCost: 2 16, // 64 MB
timeCost: 3,
parallelism: 1
});
Memory-hard: GPU and ASIC attacks become much harder because memory dominates the cost.
OWASP 2026 Parameters
- Argon2id: m=64 MB, t=3, p=1 (or m=19 MB, t=2, p=1 for tighter envs)
- bcrypt: cost 12 (cost 14 if you can afford 1s+ per login)
When to Pick Each
- New project, no constraints → Argon2id
- Existing bcrypt → keep it; rewrap on next login if you must migrate
- Hardware-constrained (small embedded) → bcrypt; Argon2 needs RAM
Don't Roll Your Own
Use the language's standard library wrappers. Never implement password hashing yourself; the constant-time comparison alone is non-trivial.
For session tokens see [JWT security best practices](https://sdk.is/blog/jwt-security-best-practices).