argon2id vs bcrypt vs scrypt: Choosing a Password Hashing Algorithm
Storing a password wrong is one of the few backend mistakes that’s still catastrophic when everything else goes right. Hashing algorithm choice is a small decision with an outsized blast radius if it’s wrong, and “just use bcrypt” — while not bad advice — isn’t the whole story anymore.
bcrypt, scrypt, and argon2id all solve the same problem (make a stolen password hash expensive to crack) with different assumptions about what an attacker has access to.
What makes a hashing algorithm “password-safe”
A general-purpose hash like SHA-256 is fast — that’s the whole point of a hash function normally, and it’s exactly the wrong property for passwords. Fast hashing means an attacker with a stolen hash database can try billions of guesses a second on cheap hardware. Password-hashing algorithms are deliberately slow, and — for the newer ones — deliberately memory-hungry, because memory is harder to parallelize cheaply than raw computation.
bcrypt
The oldest of the three still in common use, and still a reasonable choice. It’s CPU-hard but not memory-hard, which means an attacker with specialized hardware (GPUs, and especially ASICs) can parallelize cracking attempts more cheaply than its 1990s designers anticipated. It also has a hard 72-byte input limit that trips people up if they don’t know it’s there.
scrypt
Designed specifically to be memory-hard as well as CPU-hard, making GPU/ASIC parallelization meaningfully more expensive than against bcrypt. It’s a real improvement on that front, but it has more tunable parameters to get right (CPU cost, memory cost, parallelization), and getting them wrong is easier to do silently than with bcrypt’s simpler single work-factor.
argon2id
The winner of the 2015 Password Hashing Competition, and the current default recommendation. It combines resistance to GPU-cracking (via memory-hardness, like scrypt) with resistance to side-channel and tradeoff attacks (via the “id” hybrid mode, which mixes the data-independent memory access of argon2i with the faster data-dependent access of argon2d). For a new project with no legacy constraint, it’s the default that needs the least second-guessing.
When bcrypt or scrypt is still the right call
Working inside an existing system that already uses bcrypt correctly, with no compromise indicators, isn’t a strong enough reason on its own to force a re-hash migration — password hashes typically only get rehashed the next time a user logs in anyway, so a switch is usually gradual, not a hard cutover. scrypt earns its place in systems where key derivation more broadly (not just password storage) is the goal, since it was designed for that dual purpose from the start.
| bcrypt | scrypt | argon2id | |
|---|---|---|---|
| Memory-hard | No | Yes | Yes |
| Resists GPU/ASIC parallelization | Weakest of the three | Strong | Strong |
| Tunable parameters | One work factor — simple | Several — easy to misconfigure | Several, but sane defaults are common in libraries |
| Track record | Longest in production use | Long, less common for passwords specifically | Newest, current default recommendation |
| Known limitation | 72-byte input cap | More configuration risk | Slightly less battle-tested at scale than bcrypt |
FAQ
Is bcrypt insecure now?
No — bcrypt hasn’t been broken, and it’s still a reasonable choice, especially inside an existing system already using it correctly. The case for argon2id is about resisting a more capable attacker more comfortably, not about bcrypt being unsafe today.
Do I need to pick memory-hard parameters myself?
Most argon2id libraries ship a sane default profile — start there rather than hand-tuning, unless a specific resource constraint or threat model gives a real reason to deviate. Guessing at parameters is worse than using an unremarkable default.
What about SHA-256 with a salt — isn’t that enough?
No. A salt stops precomputed rainbow-table attacks, but SHA-256 is still fast, so a targeted brute-force against one stolen hash is still cheap. Salting and slow hashing solve different problems — you need both properties, and general-purpose hashes only give you the first.
Need this kind of thinking applied to your own project?
Get in touchRelated Posts
- RS256 vs HS256: Choosing a JWT Signing Algorithm for Multi-Tenant Systems
HS256 and RS256 solve the same problem differently — one shares a secret, one doesn’t. Here’s why that distinction matters more once more than one service needs to verify a token.
- A Backend & Identity Glossary: JWT, JWKS, Multi-Tenancy, and More
Plain definitions for the terms that come up most when talking about authentication and multi-tenant backend architecture — not a complete course, a working reference.