crack-time.tools

Argon2 vs bcrypt vs scrypt vs PBKDF2: choosing a password hash

If you are storing user passwords, the single most consequential decision you will make is which hash function protects them. Get this right and a stolen database is a costly incident. Get it wrong and a stolen database is a mass credential-stuffing event within hours. This guide walks through the four functions you will actually encounter in the wild, what makes them different under the hood, and which one to pick for a given constraint.

Why a plain hash is not an option

SHA-256 and SHA-512 are fast, which is exactly the wrong property for password storage. A general-purpose GPU can compute tens of billions of SHA-256 hashes per second. If an attacker steals a database of SHA-256(password) values, a weak or common password falls in well under a second. Password hashing functions solve this by being deliberately slow and, in the better designs, deliberately expensive in memory as well as time. Every one of the four functions below also handles salting internally, so two users with the same password never produce the same stored hash.

Memory-hardness: the property that actually matters now

Time-cost alone (just running more rounds of a fast primitive) scales poorly as a defense, because custom hardware (FPGAs and ASICs) can run those rounds far faster and cheaper than a general-purpose CPU. Memory-hardness closes that gap. A memory-hard function requires a large block of RAM per guess, and RAM is expensive to replicate in custom silicon at scale the way raw compute is. This is why Argon2 and scrypt, which force an attacker to allocate tens of megabytes per candidate password, resist GPU and ASIC cracking far better than bcrypt or PBKDF2, which use only a small, fixed amount of memory regardless of cost setting.

Argon2id: the current default

Argon2 won the Password Hashing Competition in 2015 and comes in three variants: Argon2d (resistant to GPU cracking, but vulnerable to certain side-channel timing attacks), Argon2i (side-channel resistant, weaker against GPU attacks), and Argon2id, a hybrid that takes the side-channel resistance of Argon2i for its first pass and the cracking resistance of Argon2d for the rest. Argon2id is what OWASP recommends and what you should default to for any new system.

The OWASP Password Storage Cheat Sheet lists five acceptable Argon2id configurations, trading memory for parallelism and time cost. Pick one based on how much RAM your authentication path can afford per login:

Memory (m)Iterations (t)Parallelism (p)
47104 KiB (~46 MiB)11
19456 KiB (~19 MiB)21
12288 KiB (~12 MiB)31
9216 KiB (~9 MiB)41
7168 KiB (~7 MiB)51

All five are considered roughly equivalent in resistance; the choice is really about your server's available memory and acceptable login latency. Higher memory with fewer iterations is generally preferred when you have the RAM, since it is harder to parallelize cheaply.

scrypt: the other memory-hard option

scrypt predates Argon2 and was designed for the same reason: to make memory, not just time, the bottleneck for an attacker. OWASP's recommended parameters are N=2^17, r=8, p=1, with a reduced option of N=2^13, r=8, p=10 for constrained environments. scrypt is a reasonable choice if your platform or library ecosystem already has mature scrypt support and no Argon2 bindings, or if you are integrating with an existing system (certain cryptocurrency wallets and some older identity providers) that already standardized on it. For new builds with a free choice, Argon2id is generally preferred because its design more deliberately separates the GPU-resistance and side-channel-resistance tradeoffs rather than bundling them into one parameter set.

bcrypt: battle-tested, with a hard limit

bcrypt has been in production since 1999 and has an excellent track record precisely because it has been attacked for over two decades without a practical break of the algorithm itself. Its weakness is that it is not memory-hard: it uses a fixed, small amount of memory regardless of its cost factor, which means GPUs and ASICs can parallelize attacks against it more efficiently than against Argon2id or scrypt at equivalent wall-clock cost. On 2026-era GPUs, bcrypt at cost factor 12 still only yields on the order of a few thousand guesses per second per GPU, which is still a meaningful defense, just a weaker one than the memory-hard alternatives at comparable settings.

bcrypt also has a quirk every implementer needs to know: it only processes the first 72 bytes of input. Anything beyond that is silently ignored. For most passwords this is irrelevant, but if you allow long passphrases, you must either enforce a 72-byte cap at the application layer or pre-hash the input with something like SHA-256 before feeding it to bcrypt, so the full passphrase actually contributes to the hash. OWASP's current minimum is a work factor of 10; most production systems today run 12 or higher.

PBKDF2: the FIPS-compliant choice

PBKDF2 is the oldest of the four (RFC 2898, 2000) and the only one that is NIST/FIPS 140 validated in standard cryptographic modules. If you work in a regulated environment (federal systems, certain healthcare or financial contexts) where FIPS compliance is a hard requirement, PBKDF2 is likely your only compliant option, since Argon2, scrypt, and bcrypt are not FIPS-approved primitives. PBKDF2 is not memory-hard at all; its only cost parameter is iteration count, which makes it the weakest of the four against GPU and ASIC attackers at any fixed wall-clock login cost. OWASP's current guidance compensates with high iteration counts depending on the underlying HMAC: 600,000 iterations for PBKDF2-HMAC-SHA256, 220,000 for PBKDF2-HMAC-SHA512, and 1,400,000 for the legacy PBKDF2-HMAC-SHA1 variant, which should only appear in systems you have not yet migrated off of.

Decision guide

OWASP's stated priority order is Argon2id, then scrypt, then bcrypt, then PBKDF2. In practice:

If you want to see exactly how parameter choices translate into real-world crack time against a given GPU budget, the hashing parameters tool lets you plug in a configuration and see the output directly rather than relying on general rules of thumb.

Migrating off a legacy hash

Almost every production system has a backlog of users still hashed with something weaker than you would choose today: an old PBKDF2-SHA1 setting, bcrypt at cost 8, or in the worst case unsalted MD5 inherited from a decade-old system. You cannot batch-rehash these, because you do not have the plaintext passwords. The standard pattern is rehash-on-login: keep the old verification path alive, and the moment a user successfully authenticates, immediately re-hash their password with your current algorithm and parameters and overwrite the stored value. Track which algorithm produced each stored hash (a version tag or prefix works well) so your verification code can dispatch to the right check. Users who never log in again will stay on the legacy hash indefinitely, which is a real residual risk worth tracking, but it is far better than a flag day that breaks every session or a forced password reset for your entire user base.

Whatever you choose, the function matters more than almost any other single control in your authentication stack. A strong, unique password hashed with a fast algorithm is more exposed than a mediocre password hashed with Argon2id. Pick the algorithm first, then worry about everything else.