
BCrypt vs Argon2 vs scrypt vs PBKDF2: Comparison
A technical comparison of BCrypt, Argon2, scrypt, and PBKDF2 for secure password storage, analyzing parameters and performance for backend developers.
Password Hashing: A Mechanism-Level Comparison
When storing passwords, developers must resist three distinct attack vectors: offline brute-forcing (guessing passwords via hash computation), rainbow table attacks (precomputed hash lookups), and side-channel attacks (inferring secrets via timing or cache behavior). Raw cryptographic hashes like SHA-256 fail here because they are computationally cheap and deterministic. Password hashing algorithms solve this by introducing "key stretching"—iteratively applying a hash function or memory-hard operations to increase the cost of guessing.
This article compares PBKDF2, BCrypt, scrypt, and Argon2id at the mechanism level, focusing on how their internal architectures dictate security properties and performance characteristics for backend systems.
PBKDF2: Iterative Key Stretching
Password-Based Key Derivation Function 2 (PBKDF2) is defined in RFC 8018 and was the NIST-recommended standard for decades. Its mechanism is straightforward: it applies a pseudorandom function (typically HMAC-SHA256) to the password and a salt repeatedly.
The security cost is determined by the iteration count (). Each hash computation requires calls to the underlying PRF. For example, with , an attacker must perform 100,000 HMAC computations per password guess.
# Python example using hashlib (PBKDF2-HMAC-SHA256)
import hashlib
import os
salt = os.urandom(16)
password = b"secret_password"
iterations = 600_000 # Recommended minimum for 2023+ based on OWASP guidelines
derived_key = hashlib.pbkdf2_hmac(
'sha256',
password,
salt,
iterations
)Mechanism Limitation: PBKDF2 is not memory-hard. It operates entirely in CPU registers and L1/L2 cache. This makes it highly vulnerable to GPU and ASIC acceleration, where thousands of parallel cores can compute PBKDF2 iterations simultaneously with minimal overhead. While secure if parameters are set aggressively, its efficiency on specialized hardware makes it less robust than modern alternatives.
BCrypt: EksBlowfish Setup
BCrypt adapts the Blowfish block cipher. Its core innovation is the EksBlowfish setup (Expensive Key Schedule). Unlike standard Blowfish, which initializes its state quickly, EksBlowfish spends significant time mixing the password and salt into the P-array and S-boxes of the cipher before any encryption occurs.
BCrypt Workflow:
1. Salt generation (128-bit random)
2. EksBlowfish Setup: Password and Salt mix into Blowfish state
- Low memory usage (~4KB per instance)
- Sequential dependency prevents parallelization of setup
3. Encryption: 64 rounds of Blowfish encryption on a known string ("OrpheanBeholderScryDoubt")
4. Output: Base64-encoded string containing parameters, salt, and hashMechanism Trade-off: BCrypt is CPU-hard, not memory-hard. The ~4KB memory requirement per instance is negligible for modern systems and does not prevent massive parallelization on GPUs or ASICs. Unlike Argon2, BCrypt’s design does not inherently provide strong side-channel resistance; while careful implementation can mitigate these risks, its fixed structure and reliance on CPU cycles make it less robust against specialized hardware attacks compared to memory-hard algorithms.
scrypt: Memory-Hardness via Random Arrays
scrypt, proposed by Colin Percival in 2009, was designed specifically to be resistant to ASICs and GPUs. Its mechanism relies on memory-hardness: the algorithm requires access to a large array of memory that cannot be precomputed or cached efficiently.
scrypt Parameters:
- N: CPU/memory cost (iteration count for the underlying block cipher)
- r: Block size parameter (controls memory bandwidth)
- p: Parallelization parameter
Memory Required ≈ 128 * N * r * p bytesFor example, with , , , scrypt requires approximately 16 MB of RAM per computation. The algorithm generates a large pseudo-random array, then repeatedly accesses it in a pattern that depends on the password. This forces the attacker to allocate significant physical memory for each parallel guess, neutralizing the advantage of GPU parallelism.
Mechanism Limitation: scrypt is vulnerable to time-memory trade-off attacks if parameters are not chosen correctly. If an attacker has access to large amounts of RAM, they can reduce the computational cost by increasing memory usage. Additionally, scrypt’s implementation complexity makes it prone to bugs, and it has seen less widespread adoption than Argon2.
Argon2id: The Hybrid Standard
Argon2 is the winner of the Password Hashing Competition (PHC) in 2015. It has three variants: Argon2d, Argon2i, and Argon2id.
- Argon2d: Optimized for GPU resistance but vulnerable to side-channel attacks.
- Argon2i: Optimized for side-channel resistance but less efficient against GPU attacks.
- Argon2id: A hybrid that uses Argon2i for the first half of the memory filling phase (to prevent side-channels) and Argon2d for the second half (to maximize GPU resistance).
RFC 9106 informationally documents Argon2id. The primary specification originates from the Password Hashing Competition, with RFC 9106 serving as an informational reference rather than a standards track standard. Its mechanism is more sophisticated than scrypt, allowing fine-grained control over memory and parallelism.
# Python example using argon2-cffi
from argon2 import PasswordHasher
ph = PasswordHasher(
time_cost=3, # Iterations
memory_cost=65536, # Memory in KB (64 MB)
parallelism=4, # Number of threads
hash_len=32, # Output length
salt_len=16 # Salt length
)
hashed = ph.hash("secret_password")
is_match = ph.verify(hashed, "secret_password")Why Argon2id Wins:
- Side-Channel Resistance: The initial Argon2i phase ensures that memory access patterns do not leak information about the password via cache timing.
- GPU/ASIC Resistance: The Argon2d phase maximizes memory bandwidth utilization, making it expensive for attackers to use specialized hardware.
- Flexibility: Parameters allow tuning for specific hardware environments (e.g., cloud servers vs. mobile devices).
Parameter Selection and Performance
Choosing parameters is critical. Under-tuning leaves your system vulnerable; over-tuning degrades user experience.
| Algorithm | Recommended Minimum (2024) | Memory Cost | Parallelism |
|---|---|---|---|
| PBKDF2-SHA256 | 600,000 iterations | Negligible | Low |
| BCrypt | Cost factor 12 | ~4 KB/instance | Low |
| scrypt | N=16384, r=8, p=1 | ~16 MB | Medium |
| Argon2id | 3 iterations, 64 MB, 4 threads | 64 MB | High |
Target Latency: Aim for a hash computation time of 200–500 ms on your target hardware. This balances security (slowing down attackers) with usability (preventing login timeouts). Use benchmarking tools like argon2-cli or passlib benchmarks to determine appropriate parameters.
Conclusion
PBKDF2 is acceptable for legacy systems but should not be used for new applications due to its vulnerability to GPU attacks. BCrypt is widely deployed and secure if configured correctly, but its fixed structure and potential side-channel issues make it less ideal than Argon2. scrypt provides strong memory-hardness but is complex to tune and less commonly supported.
Argon2id is the recommended standard for new password storage systems. It offers the best balance of side-channel resistance, GPU resistance, and configurability. Always use a cryptographically secure random salt, and regularly rehash passwords with updated parameters as hardware capabilities evolve.
Common Pitfalls
- Using Static Salts: Salts must be unique per password and generated using a cryptographically secure random number generator. Reusing salts or using predictable values (like usernames) enables rainbow table attacks.
- Not Benchmarking Parameters: Default parameters may be insufficient for current hardware capabilities or too heavy for your server. Always benchmark on your specific target hardware to ensure acceptable latency.
- Assuming BCrypt is Memory-Hard: BCrypt is CPU-hard. Treating it as equivalent to Argon2 or scrypt in terms of resistance to specialized hardware (ASICs/GPUs) is a security misconception.
Practical Takeaways
- Use Argon2id for New Projects: It provides the best balance of security features, including resistance to both side-channel and hardware-acceleration attacks.
- Benchmark PBKDF2 if Legacy Compatibility is Required: If you must use PBKDF2 due to legacy constraints, ensure iteration counts are high enough to meet modern security standards (e.g., OWASP recommendations).
- Monitor Hardware Trends: As GPU and ASIC capabilities improve, re-evaluate your hashing parameters periodically to maintain adequate security margins.
FAQ
Is BCrypt still secure? Yes, BCrypt is still considered secure if used with appropriate cost factors. However, it is less resistant to specialized hardware attacks (ASICs/GPUs) compared to memory-hard algorithms like Argon2id, making it less ideal for new implementations.
Why is Argon2id preferred over Argon2i? Argon2i is optimized for side-channel resistance but is less efficient against GPU attacks. Argon2id combines both approaches: it uses Argon2i for the initial memory filling phase to prevent side-channels and Argon2d for the rest to maximize resistance against GPU/ASIC attacks.
How often should I rehash passwords? Rehash passwords whenever a user logs in if the parameters have changed or if the previous hash is deemed insufficiently secure. This allows you to gradually upgrade the security of your database without forcing all users to log out simultaneously.
Related posts
Spring Security Password Encoding: BCrypt, Argon2, and PBKDF2
An examination of password encoding strategies in Spring Security using BCrypt, Argon2, and PBKDF2 to ensure secure password storage.
Migrating From BCryptPasswordEncoder to Argon2PasswordEncoder
A technical walkthrough for Java developers on migrating from BCryptPasswordEncoder to Argon2PasswordEncoder in Spring Security for improved password hashing security.
OIDC Dynamic Client Registration (RFC 7591)
A technical examination of RFC 7591, covering dynamic client registration, initial access tokens, and automated client management for backend systems.