Password Hashing in 2026: Argon2 vs bcrypt vs scrypt vs PBKDF2
Few engineering decisions carry as much downside risk as how you store passwords. Get it right and a database breach is an embarrassment; get it wrong and it’s a catastrophe in which millions of your users’ credentials — often reused across their email, banking, and everything else — are cracked within hours. The difference between those two outcomes is almost entirely a function of which algorithm you chose and how you configured it.
This guide is the definitive engineering reference for password storage in 2026. It explains why ordinary hashes are disqualified, compares the four legitimate password hashing functions — Argon2, bcrypt, scrypt, and PBKDF2 — and covers the surrounding mechanics (salts, peppers, work factors, and the bcrypt truncation trap) that determine whether your “secure” implementation actually is. The recommendations align with current OWASP guidance, the de facto standard for this domain.
The cardinal rule: never use a fast hash
Start with the mistake that causes the most breaches. SHA-256, SHA-1, and MD5 must never be used to store passwords. This surprises people, because these are cryptographic hashes — aren’t they secure?
They are secure for what they were designed for: verifying file integrity, signing data, building HMACs. Those use cases want speed. Password storage wants the exact opposite. Here’s the math that makes the point: a modern GPU can compute billions of SHA-256 hashes per second. If your database leaks and passwords are stored as SHA-256, an attacker runs every word in a dictionary, every common password, and every leaked-password list against your hashes and cracks the weak and medium ones almost instantly. Salting helps against precomputed tables but does nothing about raw speed — the attacker just salts their guesses too.
Purpose-built password hashing functions invert this. They are deliberately slow and resource-hungry, tunable so that a single hash takes a quarter-second to a full second. That’s invisible to a user logging in once, but it reduces an attacker from billions of guesses per second to thousands per device — a difference of six or more orders of magnitude. That gap is the entire security model. This deliberate slowness is called key stretching, and it is the foundation of every algorithm below.
The four legitimate algorithms
There are exactly four password hashing functions you should consider in 2026. Here’s how they compare at a glance, followed by the detail on each.
| Algorithm | Year | Memory-hard? | OWASP status | Best for |
|---|---|---|---|---|
| Argon2id | 2015 | Yes | First choice | New systems |
| bcrypt | 1999 | No | Recommended | Broad compatibility, existing systems |
| scrypt | 2009 | Yes | Acceptable | When Argon2 unavailable but memory-hardness wanted |
| PBKDF2 | 2000 | No | Acceptable | FIPS-140 compliance requirements |
Argon2: the modern first choice
Argon2 won the 2015 Password Hashing Competition and is OWASP’s top recommendation. Its key innovation is being memory-hard: it requires a configurable, substantial amount of RAM to compute, not just CPU time. This matters because attackers crack passwords on GPUs and custom ASICs that have thousands of parallel cores but limited memory per core. A CPU-only algorithm parallelizes beautifully on that hardware; a memory-hard one does not, because you can’t give every core enough RAM. Memory-hardness is what makes Argon2 dramatically more expensive to attack at scale.
Argon2 has three variants:
- Argon2id — a hybrid that resists both side-channel and GPU/ASIC attacks. This is the one to use.
- Argon2i — optimized against side-channel attacks only.
- Argon2d — optimized against GPU attacks only.
You tune three parameters: memory cost (how much RAM), time cost (iterations), and parallelism (threads). OWASP’s baseline guidance is around 19 MiB of memory, 2 iterations, and 1 degree of parallelism, adjusted upward to fit your latency budget. Argon2 also has no input-length limit, sidestepping bcrypt’s truncation problem.
The only real downside is availability: not every language and platform ships a well-maintained Argon2 binding, though support has grown substantially. When it’s available, choose Argon2id.
bcrypt: the dependable veteran
bcrypt, from 1999, is built on the Blowfish cipher and has protected passwords for over two decades. It is not memory-hard, but it remains secure when configured correctly, and its ubiquity is a genuine advantage — every language has a mature, audited bcrypt library.
bcrypt’s security dial is the cost factor (or work factor), and the relationship is exponential: each increment doubles the work. A cost of 12 means 2¹² = 4,096 iterations. In 2026, cost 12 or higher is the recommended floor.
bcrypt has two quirks you must know:
- The 72-byte truncation. bcrypt silently ignores everything past the first 72 bytes of input. For ASCII that’s 72 characters; for multi-byte UTF-8 it can be far fewer. A long passphrase gets cut off, and — worse — two different long passwords sharing a 72-byte prefix become interchangeable. The standard fix is to pre-hash with SHA-256 and base64-encode the result before bcrypt, compressing any-length input into a fixed, full-entropy value.
- Version prefixes. bcrypt hashes start with
$2a$,$2b$, or$2y$. Most libraries accept all three; a mismatch usually means a different concern (like a truncated database column — bcrypt hashes are always exactly 60 characters).
For existing bcrypt systems, there is no urgency to migrate to Argon2 — raising the cost factor is usually enough. For new systems, Argon2id is the better default, but bcrypt is never the wrong answer when configured well. You can experiment with bcrypt cost factors and verification using a bcrypt generator.
scrypt: memory-hard alternative
scrypt (2009) was the first widely-used memory-hard function, predating Argon2. It’s a reasonable choice when you want memory-hardness but Argon2 isn’t available in your stack. It exposes parameters for CPU/memory cost (N), block size (r), and parallelization (p). It’s secure and battle-tested (it underpins some cryptocurrencies), but Argon2 is generally preferred for new work because it’s more flexible and was purpose-designed for password hashing with the benefit of scrypt’s lessons.
PBKDF2: the compliance choice
PBKDF2 (2000) is the oldest of the four and the simplest: it applies a pseudorandom function (typically HMAC-SHA-256) many times over. Critically, it is neither memory-hard nor particularly GPU-resistant — it’s the weakest of the four against modern cracking hardware, because GPUs accelerate it well. So why use it? FIPS-140 compliance. PBKDF2 is a NIST-approved algorithm, so regulated environments (government, certain financial systems) that require FIPS validation often must use it. If you have that requirement, use PBKDF2 with a high iteration count (hundreds of thousands to over a million of HMAC-SHA-256, per current guidance). If you don’t, prefer one of the memory-hard options.
Salts: mandatory and non-negotiable
A salt is a unique, random value generated for every password before hashing. It does one essential job: it ensures that two users with the same password get different hashes. Without salts, an attacker precomputes a giant table of hash(common_password) → password (a rainbow table) once and looks up every stolen hash instantly. With a unique salt per password, that precomputation is worthless — the attacker would need a separate table for every salt.
The good news: with bcrypt and Argon2 you don’t manage salts manually. They generate a random salt and embed it directly in the output string, and the verification function extracts it automatically when checking a password. A bcrypt hash like $2b$12$<22-char-salt><31-char-hash> literally contains its own salt. You store the whole string; that’s it. Salts are not secret — their security comes from being unique, not hidden.
Peppers: optional defense-in-depth
A pepper is a second secret, distinct from the salt in two ways: it is the same for every user, and it is kept outside the database — in application configuration, an environment variable, or ideally a hardware security module or secrets manager. The usual implementation is to HMAC the password with the pepper key before feeding it to the password hash.
The pepper’s value is scenario-specific: if an attacker steals only the database (a SQL injection dump, a leaked backup), they still can’t crack the hashes without also obtaining the pepper from your separate secret store. If they compromise your whole application server, the pepper offers no protection — they have both. A pepper is genuine defense-in-depth, not a substitute for proper hashing, and it adds operational complexity (the pepper must be backed up and rotated carefully). Add it when you can manage a secret store properly; skip it if it would just become a poorly-managed config value.
Tuning the work factor over time
The work factor (bcrypt cost, Argon2 memory/time parameters) is not set-and-forget. Hardware gets faster every year, so a cost that took 300ms in 2022 is quicker now. The discipline:
- Benchmark on your production hardware. Choose parameters where a single hash takes roughly 250ms to 1 second. This is the sweet spot: invisible to a legitimate login, brutal to an attacker.
- Re-evaluate every couple of years and increase the cost as hardware improves.
- Upgrade existing hashes transparently. Because each hash embeds its own cost, you can raise the cost for new hashes immediately, and re-hash a user’s password at the higher cost on their next successful login. Over time your whole database migrates to the stronger setting without forcing password resets.
A correct implementation, end to end
Putting it together, here is the lifecycle of a securely-stored password:
- On signup, take the password, optionally HMAC it with a pepper, then hash with Argon2id (or bcrypt). The function generates a unique salt and embeds it. Store the resulting string.
- On login, take the submitted password, apply the same pepper step, and call the algorithm’s verify function with the stored hash. The function extracts the salt and cost from the stored string and recomputes — you never compare hashes manually.
- Use a constant-time comparison for the final check (the library does this for you) to avoid timing attacks.
- On successful login, if the stored hash uses an outdated (lower) cost, transparently re-hash at the current cost and update the record.
Generate strong test passwords with a password generator, and remember the meta-rule: never invent your own scheme. Use a vetted library that implements one of the four algorithms. Custom password schemes are a reliable source of catastrophic, subtle bugs.
Migrating an existing system to a stronger algorithm
Many teams inherit a password store using a weak or outdated approach — unsalted SHA-256, an ancient bcrypt cost, or worse — and face the question of how to upgrade without forcing every user to reset their password. The good news is that you can migrate transparently.
The core technique is upgrade-on-login. You cannot re-hash existing passwords directly, because you don’t have the plaintext (that’s the whole point). But you do see the plaintext the next time each user logs in. So: when a user authenticates successfully against the old hash, you immediately re-hash their just-verified password with the new, stronger algorithm and replace the stored value. Over time, as users return, your database migrates itself to the new scheme — no mass reset required. You store a marker (or detect it from the hash format) indicating which scheme each record uses, so the login code knows which verifier to apply.
For records that never log in (dormant accounts), a stronger interim measure is layered hashing: wrap the existing weak hashes in a strong algorithm immediately, hashing the old hash with Argon2 or bcrypt. For example, if you have unsalted SHA-256 hashes, compute argon2(sha256_hash) for every record at once and store that. Now even your dormant accounts are protected by a slow, salted function, and at login you apply the same wrapping to verify. This closes the window where a database leak would expose weakly-hashed dormant passwords. When those users eventually log in, upgrade them to a clean single-layer hash via upgrade-on-login.
The takeaway: there is never a reason to leave passwords in a weak scheme because “we can’t migrate without resetting everyone.” You can, and you should — transparently for active users, and immediately via layering for the rest.
Hashing is necessary but not sufficient
Strong password hashing protects credentials after a database breach, but it’s one layer in a larger defense. A complete password-security posture also includes controls at the authentication layer, because even a perfectly-hashed weak password falls to an online guessing attack if you let an attacker try unlimited times.
Rate limiting and lockout. Throttle login attempts per account and per IP so an attacker can’t simply hammer your login endpoint with guesses. Exponential backoff and temporary lockouts after repeated failures turn online brute-forcing from feasible into hopeless — without the lockout becoming a denial-of-service vector against legitimate users (which is why per-account-plus-per-IP, with sensible thresholds, beats a naive global lock).
Breached-password screening. The most common real-world attack isn’t brute force — it’s credential stuffing, where attackers replay username/password pairs leaked from other sites, exploiting password reuse. Checking new and changed passwords against known-breached-password lists (using a privacy-preserving k-anonymity API so you never send the full password) blocks the reused credentials that stuffing depends on. This single control defeats the attack that no amount of hashing strength can.
Multi-factor authentication. MFA is the strongest mitigation of all: even a fully compromised password is insufficient to log in without the second factor. For high-value accounts it should be mandatory, and time-based one-time passwords or hardware keys are far stronger second factors than SMS.
The mental model: hashing is your defense for when the database leaks; rate limiting, breach screening, and MFA are your defenses for the login endpoint itself. A serious system needs both halves. Generate strong, unique passwords to begin with using a password generator, and remember that the best password is one the user never reuses — which is why password managers and breach screening matter as much as the hash function.
Common mistakes that defeat good algorithms
- Using SHA-256/MD5 “with a salt.” Salting a fast hash doesn’t fix the speed problem. The attacker is still doing billions of (salted) guesses per second.
- A cost factor that’s too low. bcrypt at cost 4, or PBKDF2 at a few thousand iterations, is barely better than a plain hash. Tune to the 250ms–1s target.
- Forgetting bcrypt’s 72-byte limit. Long passphrases get silently truncated. Pre-hash with SHA-256.
- Rolling your own crypto. Custom implementations introduce timing leaks, salt-reuse bugs, and worse. Use audited libraries.
- Hard-coding or never rotating a pepper. A pepper checked into source control or never backed up is a liability, not a defense.
- Never increasing the cost. A cost set in 2020 is too weak today. Re-benchmark and raise it.
Conclusion
Storing passwords securely in 2026 comes down to a few firm rules. Never use a fast hash — SHA-256 and friends are for integrity, not passwords. Use a purpose-built, slow, salted function: Argon2id as the modern first choice, bcrypt as the dependable and widely-supported alternative, with scrypt and PBKDF2 filling specific niches. Let the library handle salts (they’re mandatory and embedded automatically), consider a pepper for defense-in-depth, and tune the work factor so a hash takes a quarter-second to a second on your hardware — then raise it as the years pass. Above all, never write your own scheme. The algorithms are solved; the failures come from misconfiguration and hubris. Audit your current implementation against this list today: which algorithm, what cost, salted, and pepper-protected? If any answer is “SHA-256” or “I’m not sure,” that’s where to start.