Viktar Patotski ·
· security
· 9 min read
Argon2 vs bcrypt vs scrypt: Which Password Hash to Use, and How to Tune It
The strength question first, then real numbers. Which password hashing algorithm actually protects you, what the current recommendations are, and how to tune each one to a verification time your login path can absorb.
You need to store passwords. Not the passwords themselves, the hashes. And the moment you look it up you get three names thrown at you: bcrypt, scrypt, Argon2. Every thread has a loud opinion. Most of them are years out of date.
Here is the honest answer, strength first, then the part almost nobody shows you: what each one actually costs to run, measured, so you can tune it instead of copying a magic number off a forum.
The short answer
If you are starting fresh, use Argon2id. If your platform or library cannot give you Argon2id, use scrypt. Use bcrypt when you are working with an existing system that already has it, and PBKDF2 only when a compliance rule (FIPS) forces your hand. That order is straight from the OWASP Password Storage Cheat Sheet, and the rest of this post is why it holds up, plus the numbers to tune whichever one you land on.
What these things actually are
All three are deliberately slow one-way functions built for storing passwords. That “deliberately slow” is the whole point, and it is what separates them from a plain SHA-256, which is far too fast and lets an attacker try billions of guesses a second against a stolen database.
bcrypt came out in 1999, built on the Blowfish cipher. It has a single tuning knob, the work factor (or cost), and each step up doubles the time. It has been in production for two decades, which is its main strength and also its ceiling: it is CPU-hard only. It leans on processor time, not memory, so an attacker with a rack of GPUs gets a big discount. It also has a quirk that bites people: most implementations only look at the first 72 bytes of the password and silently ignore the rest.
scrypt arrived in 2009 and added the idea that matters most today: memory hardness. It forces the machine computing the hash to hold a large block of memory the whole time. GPUs and custom cracking hardware have lots of compute but comparatively little fast memory per core, so making the function hungry for RAM is what raises the attacker’s cost, not just yours. There is an unintended bonus in 2026: with memory as expensive as it has been lately, forcing an attacker to buy RAM by the rack to parallelize a crack is a special kind of cruel. It has three knobs (N for cost, r for block size, p for parallelism), and getting them right is fiddlier than bcrypt’s single dial.
Argon2 won the Password Hashing Competition in 2015, which was the whole industry sitting down to pick a successor. It comes in three variants. Argon2d is the most resistant to GPU cracking but leaks timing information, so it is wrong for password storage. Argon2i is hardened against those side-channel attacks. Argon2id is the hybrid, and it is the one you want: side-channel resistance plus strong GPU resistance. It has clear knobs (m for memory, t for iterations, p for parallelism) and is the current recommended default.
Strength, side by side
This is the comparison people actually search for, and it has nothing to do with speed.
| Dimension | bcrypt | scrypt | Argon2id |
|---|---|---|---|
| Memory hard (GPU / ASIC resistant) | No | Yes | Yes |
| Side-channel resistant | Yes | Partial | Yes |
| Maturity in production | Very high | High | High |
| Tuning knobs | 1 (work factor) | 3 (N, r, p) | 3 (m, t, p) |
| Known footgun | 72-byte input limit | param confusion | pick the id variant |
| Standardized | de facto | RFC 7914 | RFC 9106 |
| OWASP position (2026) | legacy systems | if no Argon2id | first choice |
The single line that decides most of it is the first one. bcrypt does not make the attacker spend memory, so a GPU farm cracks bcrypt hashes far more cheaply per guess than it cracks scrypt or Argon2id. That is why the memory-hard pair sits above bcrypt today, even though bcrypt is not broken.
The parameters to actually use
The OWASP Password Storage Cheat Sheet (retrieved July 2026) gives concrete minimums. These are floors, not targets. Raise them until the time cost hits the limit your login path can tolerate.
- Argon2id: at least 19 MiB of memory, 2 iterations, 1 degree of parallelism.
- bcrypt: work factor 10 or higher. Enforce a maximum password length of 72 bytes so the truncation quirk cannot surprise you.
- scrypt: N = 2^17 (128 MiB), r = 8, p = 1.
How I measured the cost
Recommendations are easy to repeat. What is missing from most write-ups is what
these settings cost on real hardware, because that is what you tune against. So I
benchmarked all three with JMH, the standard
Java microbenchmark harness, using the exact encoders a Spring app already ships
with: Argon2PasswordEncoder, BCryptPasswordEncoder, and
SCryptPasswordEncoder from Spring Security Crypto, with Bouncy Castle as the
Argon2 and scrypt backend.
Setup: AverageTime mode, 2 forks, 3 warmup and 5 measurement iterations. Hardware and versions are disclosed in full because reproducibility is the entire point: AMD Ryzen 9 7950X3D, 96 GB RAM, Amazon Corretto JDK 25, Spring Security Crypto 6.3.4, Bouncy Castle 1.78.1. The full harness is in the repo, so you can run it on your own box, which is what you should tune against, not mine.
The numbers
Cost of one hash at the OWASP minimum parameters above:
| Algorithm | Parameters | ms per hash |
|---|---|---|
| Argon2id | 19 MiB, t 2, p 1 | 19.5 |
| bcrypt | work factor 10 | 47.0 |
| scrypt | 128 MiB, r 8, p 1 | 265.1 |
The first surprise: the strongest recommendation is also the cheapest to run. Argon2id at its floor costs about 20 ms, while scrypt at its floor costs about 265 ms because its recommended minimum demands a full 128 MiB. That is not a reason to prefer scrypt for being “harder”, it is a reason to prefer Argon2id: you get the best resistance and you still have room to push the memory well past 19 MiB before you reach the time scrypt already spends.
The second thing worth internalizing is how tuning behaves. Here is bcrypt across work factors:
| Work factor | ms per hash |
|---|---|
| 8 | 13.3 |
| 10 | 47.6 |
| 12 | 189.6 |
| 14 | 750.4 |
Each step of +1 doubles the time, so +2 is roughly 4x. A work factor of 14 already costs three quarters of a second per login on this fast a machine, which is too slow for most auth paths. This is the tuning loop for every one of these algorithms: pick a target verification time (a common budget is around 250 ms), then raise the cost knob until you hit it. For Argon2id you raise memory first, then iterations. For scrypt you raise N.
What the numbers mean
Read them backwards from how you would read a normal benchmark. Slower is the feature. Every millisecond you spend hashing is a millisecond an attacker has to spend per guess, multiplied by billions of guesses in an offline crack. The job is to spend as much time and memory as your real login traffic can absorb without users noticing, and no less.
So the decision is not “which is fastest”. It is:
- New system: Argon2id. Start at 19 MiB / t 2 / p 1, then raise memory toward a 250 ms target on your production hardware.
- Existing bcrypt: you are not in danger, but you are behind. Keep bcrypt at work factor 12 or higher for now, and rehash to Argon2id on next login (wrap the old hash, verify, then re-store with the new algorithm).
- Argon2id unavailable: scrypt at the OWASP minimum is a solid second.
- FIPS mandate: PBKDF2 with a very high iteration count, because compliance leaves you no choice, not because it is stronger.
One more practical note: memory-hard algorithms cost memory on your servers too, per concurrent login. Argon2id at 19 MiB with a hundred simultaneous logins is about 2 GB of transient memory. Size for it, and put a small semaphore in front of the hash so a login spike cannot exhaust the box.
Does quantum computing break this?
Short answer: no, and password hashing is not where the quantum risk lives. The headline quantum threat is Shor’s algorithm, which breaks the asymmetric crypto behind TLS key exchange and digital signatures (RSA, elliptic curve). That is the part of your stack to plan a migration for, not your password hashes. The only quantum lever against hashing is Grover’s algorithm, which speeds up brute force quadratically, roughly halving the effective bit strength of a preimage search. Two things blunt it. Passwords are already low-entropy, so the deliberate slowness of the KDF is the real wall, not the hash width. And memory-hard functions like Argon2id give a quantum attacker almost no discount, because Grover does not accelerate the large, high-speed memory access these functions demand. Practical quantum attacks on password hashes are estimated well over a decade out, and even then, nudging the cost parameters up (on the order of 4x) restores the margin. Spend your post-quantum budget on key exchange and signatures. Your Argon2id hashes are the least of your worries.
The verdict
Use Argon2id. It is the current best answer on strength, it is the cheapest of the three to run at its recommended floor, and it gives you the clearest path to tune upward. bcrypt is not broken, it is just CPU-only and showing its age; keep it if you have it, plan to move. scrypt is a fine memory-hard fallback when Argon2id is not on the menu. The fastest hash is never the right one. The right one is the strongest algorithm, tuned as slow as your users will tolerate.
The full JMH harness, the exact parameters, and the raw results are in the argon2-bcrypt-scrypt repo. Clone it, run it on your own hardware, and tune from your numbers.
Password storage is one control in a larger picture. If you are getting a SaaS product ready for a security review and want the engineering in order (hashing, encryption at rest, tenant isolation, audit logging), that is what I do under Security and Compliance. Related on this site: Postgres row-level security for multi-tenancy and tenant data isolation.