Why password hashing is different from general hashing
SHA-256 is designed to be fast. That speed is exactly what makes it dangerous for passwords: an attacker with a stolen password database can compute billions of SHA-256 hashes per second on a single GPU. Password hashing functions are deliberately slow (typically 100 ms per hash) and require memory (which is much more expensive than CPU). The goal is to make brute-force economically infeasible, not impossible.
bcrypt — the venerable workhorse
bcrypt was created in 1999 based on the Blowfish cipher. It has a tunable cost factor (the 'work factor') that doubles the computation time for each increment. As of 2026, a cost factor of 12–14 is reasonable. bcrypt is supported in virtually every language, and decades of cryptanalysis have not produced practical attacks. The downside: bcrypt's memory usage is fixed and small, so GPUs and FPGAs can attack it relatively efficiently.
scrypt — the memory-hard pioneer
scrypt was designed in 2009 specifically to resist hardware-accelerated attacks. It requires a large amount of memory to compute (tunable, typically 16–64 MB per hash), which makes GPU and ASIC attacks much harder because memory is expensive on those platforms. The trade-offs: configuration is more complex (three parameters: N, r, p), and the increased memory usage can be a problem on memory-constrained servers.
Argon2 — the modern winner
Argon2 won the Password Hashing Competition in 2015 and is now the recommended default. There are three variants: Argon2d (data-dependent, fast but vulnerable to side-channel attacks), Argon2i (data-independent, safer for shared-memory environments), and Argon2id (a hybrid recommended for most uses). Argon2 is the most flexible (tunable memory, time, and parallelism), the most modern, and the strongest against currently-known attacks.
Comparison table
- bcrypt: 1999, time-cost only, ~4 KB memory, widely supported.
- scrypt: 2009, memory-hard, configurable N/r/p, harder to tune correctly.
- Argon2id: 2015, configurable time/memory/parallelism, current recommendation.
Tuning parameters
All three functions have parameters that trade hash time against security. A reasonable target is ~100 ms per hash on the production server, which is imperceptible during login but enormously expensive at brute-force scale. For Argon2id, OWASP recommends 19 MiB memory, 2 iterations, and 1 parallelism as a baseline. For bcrypt, cost 12+ is the floor in 2026. For scrypt, N=2^17 (~128 MB), r=8, p=1 is a common setup.
Migration strategy
If you have an existing user base hashed with bcrypt and want to move to Argon2id, the standard approach is to re-hash at login: when a user logs in successfully, you verify their password against the old hash, then immediately re-hash with the new function and update the stored record. Over a few months, most active users will be migrated transparently. For inactive users, leave the bcrypt hash in place — it's still safe.
What about pepper?
A 'pepper' is a single secret value added to all passwords before hashing, stored separately from the database (e.g., in an HSM or an environment variable). Pepper doesn't replace salt — each user still needs their own salt — but it adds defense in depth against an attacker who steals the database but not the secret store. Recommended for high-security applications.