Password Hashing: Bcrypt vs Argon2 vs PBKDF2

Compare modern password hashing algorithms and learn which one to use in 2025, including quantum computing considerations.

CO
conv4me
October 13, 2025
8 min read
4 views

Introduction

Password hashing protects passwords even when databases get breached. A good hash is one-way: you can verify a password matches, but can’t reverse the hash to get the original password back.

Not all hashing algorithms are created equal. MD5 and SHA-256 are fast—too fast. Attackers can try billions of passwords per second with these. Modern password hashing algorithms are intentionally slow to make brute-force attacks impractical.

This guide compares bcrypt, Argon2, PBKDF2, and scrypt. You’ll learn which one to use, how quantum computing affects password security, and how to avoid costly mistakes.

How It Works

Password hashing uses three key concepts: hashing, salting, and key stretching.

Hashing converts passwords into fixed-length strings. Good hashes are:

  • One-way: Can’t reverse the hash to get the password
  • Deterministic: Same input always produces same output
  • Avalanche effect: Tiny input change completely changes the hash

Salting adds random data before hashing. This prevents rainbow table attacks.

Without salt:
password123 → hash(password123) → 5f4dcc3b5aa765d61d8327deb882cf99
(Same hash for everyone using "password123")

With salt:
password123 + random_salt → hash(password123 + salt) → unique hash
(Different hash for each user, even with same password)

Key stretching runs the hash function thousands of times. This makes each attempt slower, forcing attackers to spend more time cracking passwords.

Fast hash (bad):
1 password = 0.000001 seconds
1 billion attempts = 16 minutes

Slow hash (good):
1 password = 0.1 seconds
1 billion attempts = 3 years

Why this matters: A GTX 4090 GPU can compute 200 billion MD5 hashes per second. With bcrypt, that drops to ~100,000 hashes per second. That’s 2 million times slower.

Best Practices

1. Use Argon2id for New Projects

Why: Argon2 won the Password Hashing Competition in 2015. It’s the most modern algorithm, resistant to GPU and ASIC attacks, and includes memory-hard features that make parallel cracking expensive.

How to implement:

# Python with argon2-cffi
from argon2 import PasswordHasher

ph = PasswordHasher(
    time_cost=2,        # Number of iterations
    memory_cost=65536,  # 64 MB of memory
    parallelism=4,      # 4 parallel threads
    hash_len=32,        # 32-byte hash output
    salt_len=16         # 16-byte salt
)

# Hash password
hash = ph.hash("user_password_here")
# → $argon2id$v=19$m=65536,t=2,p=4$...

# Verify password
try:
    ph.verify(hash, "user_password_here")
    # Password is correct
except:
    # Password is wrong
// Node.js with argon2
const argon2 = require('argon2');

// Hash password
const hash = await argon2.hash('user_password_here', {
    type: argon2.argon2id,
    memoryCost: 65536,  // 64 MB
    timeCost: 2,
    parallelism: 4
});

// Verify password
const isValid = await argon2.verify(hash, 'user_password_here');

When to use: All new applications. Argon2id is the default recommendation in 2025.

Argon2 variants:

  • Argon2id: Hybrid (recommended for passwords)
  • Argon2i: Optimized against side-channel attacks
  • Argon2d: Optimized against GPU attacks (faster but less secure against side-channels)

2. Use bcrypt for Compatibility and Maturity

Why: bcrypt has been battle-tested for 25+ years. It’s widely supported, well-audited, and automatically salts passwords. Choose this if Argon2 isn’t available in your language/framework.

How to implement:

# Python with bcrypt
import bcrypt

# Hash password
password = b"user_password_here"
salt = bcrypt.gensalt(rounds=12)  # Work factor: 2^12 iterations
hash = bcrypt.hashpw(password, salt)
# → $2b$12$...

# Verify password
if bcrypt.checkpw(password, hash):
    print("Password correct")
// Node.js with bcrypt
const bcrypt = require('bcrypt');

// Hash password
const saltRounds = 12;
const hash = await bcrypt.hash('user_password_here', saltRounds);

// Verify password
const isValid = await bcrypt.compare('user_password_here', hash);

Work factor: Start with 12 rounds. Increase as hardware gets faster.

Rounds | Time per hash | Time for 1M passwords
-------|---------------|---------------------
10     | 0.1s         | 27 hours
12     | 0.3s         | 4.5 days
14     | 1.2s         | 18 days

When to use: Legacy codebases, systems where Argon2 isn’t available, or when ecosystem support is critical.

3. Avoid PBKDF2 Unless Required by Standards

Why: PBKDF2 is older and less resistant to GPU attacks than bcrypt or Argon2. However, it’s required by many security standards (FIPS 140-2, NIST) and government systems.

How to implement:

# Python with hashlib
import hashlib
import os

salt = os.urandom(32)
hash = hashlib.pbkdf2_hmac(
    'sha256',           # Hash function
    b'password',        # Password bytes
    salt,               # Salt
    100000,             # Iterations (increase for stronger security)
    dklen=32            # Output length
)

When to use: Only when compliance requires it (government, banking, healthcare). Otherwise, use Argon2 or bcrypt.

Iteration count: NIST recommends 210,000 iterations for PBKDF2-SHA256 (as of 2023).

4. Never Use MD5, SHA-1, or Plain SHA-256 for Passwords

Why: These algorithms are designed for speed, not password security. Attackers can compute billions of hashes per second on modern GPUs.

The problem:

# BAD: Fast hashing without salt or key stretching
import hashlib
hash = hashlib.sha256(b"password123").hexdigest()
# → 5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8

# An attacker can try 10 billion SHA-256 hashes per second
# This password would be cracked in < 1 second

Why it’s bad:

  • No salt (rainbow tables work)
  • Too fast (GPU can crack 10B+ hashes/sec)
  • Not memory-hard (cheap to parallelize)

The fix: Use Argon2, bcrypt, or PBKDF2 with high iteration counts.

5. Store Hashes with Algorithm Metadata

Why: Algorithms and parameters need to be upgradeable. Store the algorithm version with the hash so you can migrate users to stronger hashing over time.

How to implement:

# Good: Hash includes algorithm info
argon2_hash = "$argon2id$v=19$m=65536,t=2,p=4$..."
bcrypt_hash = "$2b$12$..."

# Detect algorithm from hash format
if hash.startswith("$argon2"):
    # Use Argon2 verification
elif hash.startswith("$2b$"):
    # Use bcrypt verification

Migration strategy:

def verify_and_upgrade(user, password, hash):
    # Verify with old algorithm
    if verify_old_hash(password, hash):
        # Rehash with new algorithm on successful login
        new_hash = argon2.hash(password)
        update_user_hash(user, new_hash)
        return True
    return False

Common Pitfalls

Using Fast Hashing Algorithms

The problem:

# Bad: Using SHA-256 directly
import hashlib
hash = hashlib.sha256(password.encode()).hexdigest()

Why it’s bad: SHA-256 runs at 10+ billion hashes/second on modern GPUs. An 8-character password gets cracked in hours.

The fix: Use Argon2, bcrypt, or PBKDF2.

# Good: Using proper password hashing
from argon2 import PasswordHasher
ph = PasswordHasher()
hash = ph.hash(password)

Not Salting Passwords

The problem:

# Bad: Same password = same hash
hash1 = hash_password("password123")  # → abc123...
hash2 = hash_password("password123")  # → abc123... (identical!)

Why it’s bad: Attackers build rainbow tables of pre-computed hashes. If 1,000 users have “password123”, all 1,000 get cracked at once.

The fix: Modern algorithms (Argon2, bcrypt) handle salting automatically. If you must use PBKDF2, generate a unique salt per password.

# Good: Each hash gets a unique salt
import os
salt1 = os.urandom(32)
salt2 = os.urandom(32)
hash1 = pbkdf2(password, salt1)  # Different hash
hash2 = pbkdf2(password, salt2)  # Different hash

Using Weak Parameters

The problem:

# Bad: Too few iterations
bcrypt.gensalt(rounds=4)  # Only 2^4 = 16 iterations (cracks in seconds)

Why it’s bad: Low work factors make hashing fast, defeating the purpose of key stretching.

The fix: Use recommended parameters for each algorithm.

# Good: Strong parameters
bcrypt.gensalt(rounds=12)  # 2^12 = 4,096 iterations

argon2.hash(password,
    time_cost=2,
    memory_cost=65536,  # 64 MB
    parallelism=4
)

Algorithm Comparison Table

Algorithm Year Speed GPU Resistance Memory Hard Quantum Safe Recommendation
Argon2id 2015 Slow ✅ Excellent ✅ Yes ⚠️ Partial ✅ Use for new projects
bcrypt 1999 Slow ✅ Good ❌ No ⚠️ Partial ✅ Use if Argon2 unavailable
scrypt 2009 Slow ✅ Good ✅ Yes ⚠️ Partial ⚠️ Use Argon2 instead
PBKDF2 2000 Medium ⚠️ Fair ❌ No ⚠️ Partial ⚠️ Only for compliance
SHA-256 2001 Fast ❌ Poor ❌ No ⚠️ Partial ❌ Never for passwords
MD5 1992 Fast ❌ Poor ❌ No ❌ No ❌ Broken, never use

Notes:

  • GPU Resistance: How well the algorithm resists GPU-based cracking
  • Memory Hard: Requires significant RAM, making ASIC attacks expensive
  • Quantum Safe: No current password hash is fully quantum-resistant. Strong passwords (20+ chars) remain secure.

Quantum Computing Considerations

Current status: Quantum computers threaten encryption (RSA, ECC) but not password hashing directly.

Why password hashes are relatively safe:

  • Hash functions (SHA-2, SHA-3) have no known efficient quantum attacks
  • Grover’s algorithm provides ~2x speedup (not exponential like Shor’s algorithm)
  • A 256-bit hash requires 2^128 quantum operations (still impractical)

What this means:

  • Short passwords remain vulnerable: Quantum or not, “password123” cracks instantly
  • Long passwords stay secure: 20+ character passwords are safe against quantum attacks
  • Hash algorithms don’t need changing: Argon2, bcrypt, and SHA-256 remain quantum-resistant

Future-proofing:

  1. Enforce minimum 16-character passwords (better: 20+)
  2. Use Argon2id with high memory costs
  3. Monitor post-quantum cryptography standards (NIST PQC)

Bottom line: Focus on password length, not quantum-resistant hashing. A 20-character password is more important than the hashing algorithm.

Quick Reference Checklist

Choosing a password hashing algorithm:

  • Use Argon2id (recommended for 2025)
  • Use bcrypt if Argon2 isn’t available
  • Use PBKDF2 only for compliance requirements
  • Never use MD5, SHA-1, or plain SHA-256

Implementation checklist:

  • Hash passwords server-side (never client-side only)
  • Use unique salt per password (automatic in Argon2/bcrypt)
  • Store algorithm metadata with hash (for future upgrades)
  • Use strong parameters (bcrypt rounds ≥ 12, Argon2 memory ≥ 64MB)
  • Increase work factors as hardware improves

Security checklist:

  • Enforce minimum 12-character passwords (16+ recommended)
  • Rehash passwords with stronger algorithms on user login
  • Use timing-safe comparison for hash verification
  • Log failed authentication attempts
  • Implement account lockout after repeated failures

Standards and References

  • OWASP Password Storage Cheat Sheet: Best practices for password hashing
  • NIST SP 800-63B: Digital identity guidelines (recommends PBKDF2 with 210k+ iterations)
  • Password Hashing Competition: Argon2 winner (2015)
  • RFC 2898: PBKDF2 specification
  • RFC 9106: Argon2 specification (draft)

Summary

Use Argon2id for new projects. It’s the most secure option against modern attacks. Use bcrypt if Argon2 isn’t available—it’s battle-tested and widely supported. Avoid PBKDF2 unless compliance requires it.

Never use MD5 or plain SHA-256 for passwords. They’re designed for speed, not security. Attackers can compute billions of hashes per second on GPUs.

Quantum computing doesn’t break password hashing. Focus on password length (16+ characters) and proper algorithm selection (Argon2 or bcrypt).

Key takeaways:

  1. Argon2id is best: Memory-hard, GPU-resistant, modern standard
  2. bcrypt is solid: 25+ years of battle testing, excellent fallback
  3. Never use MD5/SHA: Too fast, attackers can crack billions per second
  4. Quantum isn’t the threat: Short passwords are. Enforce 16+ characters.
  5. Upgrade over time: Rehash passwords with stronger algorithms on user login

Try It Yourself

Head over to our tools and experiment with the concepts discussed in this article.