Learn · Technologies we use
Password hashing: bcrypt, scrypt, Argon2 and PBKDF2
Why a plain hash isn't enough, what memory-hardness buys against GPUs, current parameters, and why a browser tool still uses PBKDF2.
A password hash is a hash function made slow on purpose. When a site stores passwords, or software turns a passphrase into an encryption key, an attacker who gets hold of the stored values will try billions of guesses offline. An ordinary hash like SHA-256 is built to be fast, which helps that attacker. A password-hashing function adds a cost you can raise as hardware improves, a unique salt so guesses can't be shared between users, and in the better designs a demand for memory that makes graphics cards and custom chips far less efficient. Here we compare the four that matter, PBKDF2, bcrypt, scrypt and Argon2, and explain why a tool that runs in a browser picks the one it does.
What goes wrong with a plain hash
Say a database stores SHA-256(password). Whoever steals it hashes a dictionary of likely passwords and looks for matches. One modern graphics card computes billions of SHA-256 hashes a second, so a short or common password falls in moments. And every user with the same password has the same stored value, so cracking one cracks all of them. A random salt per user fixes the sharing. Making each guess cost much more is the other half, and that's what the functions below do. The numbers from one real benchmark, and what they mean for a passphrase of a given strength, are in the PBKDF2 article and the crack-time estimator.
PBKDF2: iterations of a keyed hash
PBKDF2 (RFC 8018, from RSA's PKCS #5) runs HMAC over the password and salt c times, each round depending on the one before, and mixes the results together (HMAC). Its only cost is time. It needs a few hundred bytes of memory, so an attacker with a GPU can run an enormous number of guesses in parallel, each needing almost nothing. It's old, simple and available everywhere, and it's the algorithm NIST approves, so it's the usual answer when FIPS 140 compliance is required. OWASP's current minimum is 600,000 iterations of HMAC-SHA-256.
bcrypt: a slow cipher setup
Niels Provos and David Mazières designed bcrypt and presented it at USENIX in 1999. It's built on the Blowfish cipher, and its expensive step is Blowfish's key setup, repeated 2cost times. It works on a state of about 4 KB that sits awkwardly in the smallest GPU caches, which gives it some resistance to massively parallel hardware. The cost is a power of two, so cost 12 means 4,096 rounds of the expensive step. The limits are real. It takes a 16-byte salt and reads only the first 72 bytes of the password, so longer passphrases get silently truncated, and NUL bytes need care. Its strings look like $2b$12$…, where the 2b arrived in February 2014 to fix a length bug in one implementation. OWASP lists a work factor of 10 as a floor and calls bcrypt a legacy choice.
scrypt: making memory the cost
Colin Percival's scrypt (2009; RFC 7914) was the first mainstream memory-hard function. It fills a big table with values that each depend on the last, then reads the table back in an order the data decides. The table has to stay in memory, because recomputing it on demand is expensive. If a guess needs 128 MiB, a graphics card with 24 GB can hold only about 190 guesses at a time, where its cores could otherwise run thousands or millions. OWASP's minimum for scrypt is N = 217 (128 MiB), r = 8, p = 1. Its parameters are intertwined (time and memory rise together), which makes it harder to tune than Argon2.
Argon2: the competition winner
By the early 2010s, after big password breaches at RockYou, LinkedIn and Adobe, cryptographers organized an open Password Hashing Competition, announced in 2013 and modeled on the AES and SHA-3 contests. On July 20, 2015, it named a winner: Argon2, designed by Alex Biryukov, Daniel Dinu and Dmitry Khovratovich at the University of Luxembourg. Catena, Lyra2, yescrypt and Makwa got special recognition. RFC 9106 specifies Argon2, in three variants. Argon2d is fastest and uses data-dependent memory access, which exposes it to side-channel attacks. Argon2i is data-independent, resists side channels, and is weaker against tradeoff attacks. Argon2id makes its first half-pass independent of the data and the rest data-dependent, and it's the one to use. Its three dials, memory, passes and parallelism, can be set separately.
Parameters today
These are OWASP's minimum configurations, from its Password Storage Cheat Sheet. They move as hardware does, so check the current page before you build anything.
| Function | Minimum setting | Memory | When to use it |
|---|---|---|---|
| Argon2id | m = 19 MiB, t = 2, p = 1 (or 46 MiB with t = 1; 12 MiB with t = 3) | 19 MiB or more | First choice for new systems |
| scrypt | N = 217, r = 8, p = 1 | 128 MiB | If Argon2id is not available |
| bcrypt | work factor 10 or more; passwords up to 72 bytes | About 4 KB | Legacy systems |
| PBKDF2-HMAC-SHA-256 | 600,000 iterations | Almost none | When FIPS 140 is required, or nothing else is available |
Salt, pepper and the rest
- A salt is a random value of at least 16 bytes, stored beside each hash and used once. It needn't be secret. It stops precomputed tables and makes every user's hash independent.
- A pepper is one secret kept outside the database (in application configuration or a hardware module) and mixed into every hash, so a stolen database alone isn't enough to start guessing. It helps, but it has to live somewhere separate, and changing it means rehashing everything.
- Upgrade on login. Store the algorithm and its parameters in each hash string, check a password with the old settings, and on success rehash with the new ones.
- Compare in constant time and treat every failure alike.
- No password hash can rescue a weak password. A passphrase with 60 bits of entropy holds out a long time even at ten billion guesses a second, and a password with 20 bits falls instantly. Length and randomness do most of the work (passphrase generator).
Why a browser tool uses PBKDF2
Argon2 and scrypt do better against attackers with graphics cards, and cipher.ink still uses PBKDF2. The reasons are practical. We think the trade-off is an honest one, though it isn't the ideal one:
- It's built in.
crypto.subtle.deriveKeyruns PBKDF2 natively in every browser, with nothing to download and nothing extra to trust (the Web Crypto API). The standard has no Argon2 or scrypt. - The alternative is WebAssembly or JavaScript. A memory-hard function in JavaScript is slow. In WebAssembly it needs a binary blob that our Content-Security-Policy forbids and that nobody could audit by reading it.
- The defender's work factor has to be tolerable on a phone. The 600,000 rounds take a fraction of a second natively. A memory-hard function sized for a phone would be modest in memory, which narrows the gap.
The cost of that choice is the one named above: PBKDF2 hands a GPU attacker a bigger advantage than a memory-hard function would. We offset it where we can. The generated passphrases carry about 47.3 bits of entropy, and a secret link skips the passphrase entirely, using a 256-bit random key that no password-guessing attack can touch.
How cipher.ink uses this
The site never stores a password, because it has no accounts. A passphrase is used on your device only, to derive an AES key through PBKDF2-HMAC-SHA256 at 600,000 rounds with a fresh 16-byte salt per message, and neither the passphrase nor the key reaches the server. So the passwords-in-a-database problem that most of this page covers doesn't exist here. A version of it does exist for an attacker holding a stolen ciphertext, who'd have to run the 600,000 rounds for every guess. That's the cost our passphrase advice is sized against.
On the timeline
- 2009 Colin Percival introduces scrypt, a memory-hard password hash.
- 20 July 2015 Argon2 wins the Password Hashing Competition.
Further reading
- OWASP Password Storage Cheat Sheet
- RFC 9106: Argon2, the Memory-Hard Function for Password Hashing and Proof-of-Work Applications (2021)
- RFC 7914: The scrypt Password-Based Key Derivation Function (2016)
- Provos and Mazieres: A Future-Adaptable Password Scheme (1999)
- Password Hashing Competition
- RFC 8018: PKCS #5: Password-Based Cryptography Specification Version 2.1 (2017)