Bcrypt, scrypt, and Argon2: Choosing a Password Hasher
When a user creates a password, you must never store the raw value. If your database is breached, plaintext passwords give attackers immediate access to every account — and since people reuse passwords, they give access to accounts on other services too. The solution is one-way hashing, but not all hash functions are created equal for this purpose.
Why fast hashes fail for passwords
General-purpose hash functions like SHA-256 and SHA-512 are designed to be fast. Fast is good for checksums and data integrity, but catastrophic for password storage. A modern GPU can compute billions of SHA-256 hashes per second, which means an attacker who steals your database can attempt billions of password guesses per second against every hash in it.
Password hashing algorithms are deliberately slow. They force a configurable amount of computational work per hash, so that even if an attacker has your database, brute-forcing one password takes seconds rather than microseconds. The attacker's GPU advantage still exists, but the search space becomes tractable only for very short or common passwords.
Bcrypt
Bcrypt (1999) was the first widely adopted slow password hasher. It is based on the Blowfish cipher and its cost is controlled by a single work factor (also called cost or rounds), an integer typically between 10 and 14. The number of hash iterations doubles with each increment: cost 10 means 2¹⁰ (1,024) iterations; cost 12 means 4,096.
Bcrypt automatically generates and stores a 128-bit salt alongside the hash, so identical passwords produce different stored values. The output is a self-contained 60-character string that encodes the algorithm, version, cost, salt, and hash.
The main limitation of bcrypt is that it truncates passwords at 72 bytes. A password longer than that provides no additional security because the extra bytes are silently ignored. Some implementations pre-hash with SHA-256 to work around this, but that introduces its own complexity. For new systems, prefer Argon2.
Recommended cost factor: start at 12 and benchmark — aim for 100–300 ms per hash on your login server hardware.
scrypt
scrypt (2009) was designed to be both computationally expensive and memory-hard. While bcrypt can be parallelized cheaply on GPU hardware, scrypt forces large memory allocations per hash attempt, making it significantly more expensive to run on GPUs and custom ASICs.
scrypt has three parameters: N (CPU/memory cost, must be a power of 2), r (block size), and p (parallelization factor). A common starting point is N=16384, r=8, p=1, which requires about 16 MB of memory per hash.
scrypt is solid but its parameter space is harder to reason about than Argon2's, and it is less commonly included in standard libraries by default.
Argon2
Argon2 won the Password Hashing Competition in 2015 and is now the OWASP recommendation for new systems. It comes in three variants:
- Argon2d — maximally resistant to GPU cracking; vulnerable to side-channel attacks. Use only in contexts without side-channel risk.
- Argon2i — resistant to side-channel attacks; less resistant to GPU cracking. Use for key derivation from passphrases.
- Argon2id — a hybrid of both. Use this for password storage.
Argon2id has three tunable parameters:
- Memory cost (
m) — kilobytes of RAM required per hash. More memory = harder to parallelize on GPUs. OWASP minimum: 19 MB (19,456 kB); 64 MB or higher for sensitive applications. - Time cost (
t) — number of passes over memory. Start at 2 or 3. - Parallelism (
p) — number of threads. Match your server's core count; typically 1 for most web apps.
Choosing and tuning
Use Argon2id for new applications. Fall back to bcrypt (cost ≥ 12) only if your language or framework doesn't have a maintained Argon2 library. Never use MD5, SHA-1, SHA-256, or any other general-purpose hash directly for passwords — even with a salt.
The right cost parameters are those that make each hash take 100–300 ms on your login server. Benchmark on your actual hardware, not your development machine. As hardware gets faster over time, increase the cost factor and re-hash passwords at next login.
Store the full output string, which encodes algorithm, version, parameters, salt, and hash. This means you can transparently migrate to stronger parameters without forcing all users to reset their passwords.
Need to generate a pepper or app-level secret? Generate a cryptographically random value in your browser.
Open the key generator