Storing passwords is one of those things that developers get wrong more often than they should. Not because the concepts are hard, but because the ecosystem has evolved and old habits die hard. If you're still reaching for MD5, SHA-256, or even unsalted bcrypt without thinking, this article is for you.
Here's a practical breakdown of the three serious contenders in 2025: bcrypt, Argon2, and scrypt.
Why Password Hashing Is Different from Regular Hashing
General-purpose hash functions (SHA-256, MD5) are designed to be fast. That's great for checksums and integrity verification — terrible for passwords. An attacker with a GPU can compute billions of SHA-256 hashes per second.
Password hashing algorithms are intentionally slow and resource-intensive. They're designed to make brute-force and dictionary attacks economically infeasible. The three we're comparing all do this, but in meaningfully different ways.
Compare hashing algorithms for passwords
✓ Best choice — memory-hard, PHC winner
Bcrypt
Released: 1999. Yes, it's older than most of the developers using it.
How it works
Bcrypt uses a cost factor (work factor) — a number that determines how many iterations the algorithm runs. Each increment doubles the computation time.
cost factor 10 → ~100ms
cost factor 12 → ~400ms
cost factor 14 → ~1600ms
Strengths
- Mature, battle-tested, widely supported across virtually every language and framework
- Simple API — hard to misconfigure
- Output includes the salt, version, and cost factor (self-describing hash)
Weaknesses
- Password length limit: bcrypt truncates input at 72 bytes. Passwords longer than 72 characters get silently truncated — a real security edge case.
- Not memory-hard: Attackers can parallelize cracking using ASICs and GPUs more effectively than against memory-hard algorithms.
- Cost factor is a single dimension — you can't independently tune memory vs CPU.
When to use it
You're on a legacy stack, you need maximum library compatibility, or you're maintaining existing systems. For new projects, prefer Argon2id.
Argon2
Released: 2015. Won the Password Hashing Competition in 2015. OWASP's top recommendation as of 2024.
Variants
- Argon2d: Maximizes resistance to GPU attacks. Vulnerable to side-channel attacks. Don't use for passwords.
- Argon2i: Resistant to side-channel attacks. Slightly weaker against GPU attacks.
- Argon2id: Hybrid of both. Use this one.
Tuning parameters
Argon2 gives you three levers:
- Memory cost (
m): RAM in KiB to use - Time cost (
t): Number of iterations - Parallelism (
p): Number of threads
OWASP recommended minimums (2024):
m=19456(19 MiB),t=2,p=1- Or
m=65536(64 MiB),t=1,p=4
Strengths
- Memory-hard by design — makes GPU/ASIC attacks significantly more expensive
- Flexible tuning
- No arbitrary password length limits
- Modern, actively maintained
Weaknesses
- Slightly less universal library support than bcrypt (though coverage is now very good)
- More parameters means more ways to misconfigure if you're not careful
scrypt
Released: 2009. Designed by Colin Percival for Tarsnap.
How it works
scrypt is also memory-hard, parameterized by:
- N: CPU/memory cost (power of 2)
- r: Block size
- p: Parallelization factor
Strengths
- Memory-hard — better than bcrypt against GPU attacks
- Available in most standard libraries (OpenSSL, Node crypto, etc.)
Weaknesses
- Tuning is less intuitive than Argon2 —
N,r,pinteract in non-obvious ways - OWASP now recommends Argon2id over scrypt for new systems
- Less actively maintained/updated compared to Argon2
When to use it
When Argon2 isn't available in your environment and you need memory-hardness. It's a solid second choice.
Code Examples
Side-by-Side Comparison
| Feature | bcrypt | Argon2id | scrypt |
|---|---|---|---|
| Memory-hard | ❌ | ✅ | ✅ |
| GPU resistance | Medium | High | High |
| Tuning flexibility | Low (1 param) | High (3 params) | Medium |
| Password length limit | 72 bytes | None | None |
| OWASP recommended | Acceptable | ✅ First choice | Acceptable |
| Library support | Excellent | Very good | Good |
| Complexity | Low | Medium | Medium |
Migration Strategy: Moving to Argon2id
You can't re-hash passwords without the plaintext — so migrate on next login:
- Add an
algorithmfield to your users table - On successful login, check if the stored hash uses the old algorithm
- If so, re-hash with Argon2id and update the stored hash
- Over time, old algorithm hashes disappear naturally
PHP's password_needs_rehash() does this automatically. For other languages, check the hash prefix — Argon2id hashes start with $argon2id$.
Use Argon2id for any new system in 2025. The library support is solid across all major languages, OWASP backs it, and the memory-hardness gives you a meaningful security advantage over bcrypt against modern hardware attacks.
If you're on a legacy bcrypt system with a high cost factor (≥12), you're not in immediate danger — but plan a migration. If you're building something new and reaching for bcrypt out of habit, stop and use Argon2id instead.
The one thing that matters more than which algorithm you pick: don't roll your own. Use a maintained library, follow OWASP's parameter recommendations, and let the algorithm do the work.
Frequently Asked Questions
Related posts
Secure Password Reset Tokens — Expiry, Storage, and What Most Implementations Get Wrong
A practical guide to building secure password reset flows: token generation, expiry windows, one-time use enforcement, and the edge cases that cause real account takeovers.
Mar 30, 2026 · 7 min readIncident Response for Developers: What to Do When You Get Hacked
A practical incident response guide for developers covering detection, containment, eradication, recovery, and communication when a security breach happens.
Mar 29, 2026 · 9 min readPhishing Prevention: A Developer's Guide to SPF, DKIM, and DMARC
Understand how email spoofing enables phishing attacks and how to implement SPF, DKIM, and DMARC to protect your domain from being impersonated.
Mar 29, 2026 · 9 min read