Skip to content
Cryptography

PBKDF2 vs bcrypt vs Argon2: Password Hashing Guide

9 min read·cyber.encse.com Knowledge Base·Last reviewed 12 Aug 2026

Storing passwords is a solved problem in the sense that the right primitives have existed for years, yet misconfigured password hashing remains one of the most common findings in application security reviews. PBKDF2, bcrypt, and Argon2 all serve the same purpose — turning a password into a slow-to-compute, salted hash that resists offline cracking — but they differ substantially in resistance to modern cracking hardware, and the right choice (and parameters) matters more than it might appear.

Why plain hashing isn't enough

A fast general-purpose hash like SHA-256 is the wrong tool for password storage precisely because it's fast: an attacker with a stolen hash database can test billions of candidate passwords per second on commodity GPUs. Password hashing functions are deliberately slow and, in the case of bcrypt and Argon2, deliberately memory-intensive, so that the attacker's cost per guess stays high even at scale.

Salting (a unique random value per password, stored alongside the hash) is necessary but separate from this — it defeats precomputed rainbow-table attacks and ensures identical passwords don't produce identical hashes. All three algorithms discussed here incorporate salting; the differentiator between them is how they resist brute-force and specialized hardware attacks.

Comparison overview

The three algorithms represent successive generations of password hashing design, each addressing a limitation in the one before it.

AlgorithmMemory-hardGPU/ASIC resistancePassword length limitCurrent recommendation
PBKDF2-HMAC-SHA256No — CPU-bound onlyWeak — highly parallelizable on GPUsNone practicalAcceptable when FIPS validation is required; use OWASP's current recommended minimum iteration count
bcryptModest, fixed small memory footprintBetter than PBKDF2, weaker than Argon272 bytes — input silently truncated beyond thisSolid default where Argon2 isn't available
Argon2idYes — tunable memory, time, and parallelism costStrongest — memory cost specifically targets GPU/ASIC economicsNone practical (implementation-dependent limits are very high)OWASP-recommended first choice

PBKDF2: CPU-bound and parallelizable

PBKDF2 applies a pseudorandom function (typically HMAC-SHA256) repeatedly to a password, with the iteration count as the sole tunable cost parameter. Its weakness is architectural: because it uses negligible memory, an attacker can run enormous numbers of parallel instances on GPU or ASIC hardware, where memory bandwidth — not compute — is usually the limiting factor for cracking speed. PBKDF2 remains relevant mainly where FIPS 140 validation is a hard compliance requirement, since it's the password-hashing construction most widely available in FIPS-validated cryptographic modules.

If you're required to use PBKDF2, iteration count is the only lever you have, and it needs to be aggressive. OWASP publishes and periodically updates a recommended minimum iteration count for PBKDF2-HMAC-SHA256 — check OWASP's current Password Storage Cheat Sheet for the current recommended minimum rather than relying on a fixed number, since it is revised upward as hardware improves.

PBKDF2 usage (Node.js crypto)

const crypto = require("crypto");

function hashPassword(password, salt) {
  // Use OWASP's current recommended minimum iteration count for
  // PBKDF2-HMAC-SHA256 — check the current OWASP Password Storage
  // Cheat Sheet, as this number is revised upward over time.
  const iterations = 600000; // verify against current OWASP guidance before deploying
  return crypto.pbkdf2Sync(password, salt, iterations, 32, "sha256").toString("hex");
}

const salt = crypto.randomBytes(16).toString("hex");
const hash = hashPassword("user-supplied-password", salt);

bcrypt: the long-standing default

bcrypt, based on the Blowfish cipher's key schedule, has a modest but fixed memory footprint that gives it meaningfully better GPU resistance than PBKDF2 without requiring the caller to tune memory parameters at all — only a work factor (cost). It has been a safe, well-audited default for over two decades.

bcrypt's sharpest pitfall is its 72-byte input limit: passwords longer than 72 bytes are silently truncated by most implementations, meaning a 100-character passphrase and the same passphrase truncated to 72 bytes hash identically. This is rarely a practical security issue for typical passwords but has caused real bugs — most notably where inputs were pre-hashed or otherwise expanded before reaching bcrypt, causing unexpected collisions. Applications with very long passphrase support should pre-hash with SHA-256 before bcrypt, or use Argon2 instead, which doesn't share this limitation.

bcrypt usage (Node.js, bcrypt package)

const bcrypt = require("bcrypt");

const saltRounds = 12; // work factor — tune to ~250ms hash time on your production hardware

async function hashPassword(password) {
  // Note: inputs beyond 72 bytes are truncated by bcrypt — be aware
  // if your application accepts long passphrases.
  return bcrypt.hash(password, saltRounds);
}

async function verifyPassword(password, hash) {
  return bcrypt.compare(password, hash);
}

Argon2id: the current recommendation

Argon2 won the 2015 Password Hashing Competition and is explicitly memory-hard: it requires a configurable amount of memory during hashing, which directly raises the cost of building parallel cracking hardware, since GPUs and ASICs get their speed advantage from cheap parallel compute, not cheap parallel memory. Argon2 comes in three variants — Argon2d (maximizes GPU resistance, vulnerable to side-channel timing attacks), Argon2i (side-channel resistant, weaker GPU resistance), and Argon2id (a hybrid). OWASP recommends Argon2id specifically as the default choice for password hashing, since it balances both properties.

Argon2 exposes three cost parameters — memory size, iteration count, and parallelism — which gives more tuning flexibility than bcrypt but also more ways to misconfigure it. OWASP's Password Storage Cheat Sheet publishes baseline parameter recommendations; the right values ultimately depend on your server hardware and acceptable login latency, and should be load-tested rather than assumed.

  • •Tune memory cost first — it's the parameter most responsible for GPU/ASIC resistance
  • •Increase iteration count as a secondary lever once memory cost is set appropriately for your infrastructure
  • •Set parallelism to match the number of cores you're willing to dedicate per hash operation on your auth servers
  • •Re-benchmark parameters when you change server hardware — cost settings are only meaningful relative to actual compute available to an attacker vs. your server

Argon2id usage (Node.js, argon2 package)

const argon2 = require("argon2");

async function hashPassword(password) {
  return argon2.hash(password, {
    type: argon2.argon2id,
    memoryCost: 19456, // ~19 MiB — validate against current OWASP baseline recommendations
    timeCost: 2,
    parallelism: 1,
  });
}

async function verifyPassword(password, hash) {
  return argon2.verify(hash, password);
}

Migration guidance

If you're running PBKDF2 or an older bcrypt work factor today, migrating existing password hashes without forcing a mass reset is a common operational question. The standard pattern is transparent re-hashing on next login: keep the old verification logic in place, and on successful login with the old algorithm, immediately re-hash the plaintext password (which you have in memory at that point) with the new algorithm and update the stored hash. Over time, the user base migrates without a forced reset event.

Never attempt to convert an existing hash to a different algorithm without the plaintext password — hashes are one-way by design, and there is no valid shortcut around this.

References

Primary sources for the material above. Standards are cited by identifier so they stay findable as publishers reorganise their sites.

  1. OWASP Password Storage Cheat Sheet
  2. NIST SP 800-63B — Digital Identity Guidelines: Authentication and Lifecycle Management
  3. RFC 9106 — Argon2 Memory-Hard Function for Password Hashing and Proof-of-Work Applications