Skip to content

· 3 min read · 694 words

Bcrypt vs Argon2 vs Scrypt vs PBKDF2

What separates the four password hashing functions in practice, which one to pick for a new project, and why switching an existing one is rarely the best use of your time.

  • bcrypt
  • argon2
  • scrypt
  • pbkdf2

All four are deliberately slow functions for storing passwords. They differ in what they make expensive, and that difference is the whole story.

The one line version

  • New project with a maintained Argon2 library available: Argon2id.
  • Existing bcrypt database that works: leave it, raise the cost factor.
  • Environment where you only get what the platform ships: PBKDF2, with a high iteration count.
  • Scrypt: fine, well studied, and rarely the answer to a question you actually have.

What each one makes expensive

PBKDF2

PBKDF2 repeats a fast hash such as SHA-256 many times. That is the entire design. It costs CPU time and almost no memory, which is exactly the shape of work a GPU is built for. An attacker with a graphics card gets a very large speedup over your server, so PBKDF2 needs a genuinely high iteration count to hold the line. OWASP currently points at 600,000 iterations for SHA-256.

Its advantage is availability. It is in the standard library of nearly everything, it is what FIPS validated environments allow, and WebCrypto gives it to you in the browser with no dependency at all.

Bcrypt

Bcrypt runs a modified Blowfish key schedule in a loop. It needs about 4 KB of memory that it accesses in an unpredictable pattern, which is small but awkward, and it happens to be a poor fit for how GPUs are wired. That accident of design is why a function from 1999 is still standing.

It has one tuning knob, the cost factor, and the 72 byte input limit. Both are easy to live with once you know about them.

Scrypt

Scrypt was the first widely used design to make memory the cost rather than time. You set how much memory a single hash needs, and an attacker has to provide that memory for every guess running in parallel. Memory is the expensive part of custom cracking hardware, so this raises their cost in a way that iterations alone do not.

It has three interacting parameters, N, r and p, and getting them wrong is easier than it should be. Setting N high with r left at 1 gives you far less memory use than the number suggests.

Argon2

Argon2 won the Password Hashing Competition in 2015 and is what the current guidance recommends. It separates memory, time and parallelism into three parameters you set independently, which makes it possible to say what you actually want rather than deducing it from an interaction.

Use the id variant. Argon2i resists side channel observation but is weaker against time and memory tradeoff attacks, Argon2d is the reverse, and Argon2id combines the two approaches. It is the variant every current recommendation names.

Side by side

PBKDF2BcryptScryptArgon2id
Year2000199920092015
Cost isCPU timeCPU timeMemoryMemory and time
Memory per hashnegligibleabout 4 KBtunabletunable
GPU resistanceweakdecentgoodgood
Tuning knobs1133
Input limitnone72 bytesnonenone
In standard librariesalmost alwaysoftensometimessometimes

Reasonable starting parameters

These follow the current OWASP cheat sheet. Tune them until a single hash takes somewhere in the range of 250 to 500 milliseconds on your own hardware, then stop.

Argon2id   m = 19 MiB, t = 2, p = 1
           or m = 47 MiB, t = 1, p = 1

bcrypt     cost = 12

scrypt     N = 2^17, r = 8, p = 1

PBKDF2     600,000 iterations with SHA-256

Should you migrate?

If you already run bcrypt at a sensible cost factor, migrating to Argon2 is a small improvement for a real amount of work. Raising your cost factor is cheaper and buys you more. Spend the time on rate limiting your login endpoint and checking passwords against a breach list instead, because both of those stop attacks that no hash function can.

If you are on unsalted MD5 or SHA-1, that is a different conversation and you should move now. Wrap the old hash inside the new one so you can migrate without waiting for logins, and drop the wrapper as accounts come through.

The migration write up covers doing this without forcing a password reset.

Read next