โ† Back to Blog

bcrypt vs MD5 for Password Storage

2026-04-18 ยท 5 min read

Core Problem: Why MD5 Can't Store Passwords

MD5's fundamental problem for password storage is speed. MD5 can compute billions of hashes per second โ€” an advantage for data processing but a fatal flaw for password security. When a database is breached, an attacker with MD5 hashes can crack them using: rainbow tables (pre-computed hashโ†’password lookup tables), brute force (billions of password combinations per second on modern GPUs), dictionary attacks (using common password wordlists). Most weak passwords' MD5 hashes can be cracked in seconds or even milliseconds.

bcrypt's Design Philosophy: Deliberately Slow

bcrypt was designed by Niels Provos and David Maziรจres in 1999, with the core philosophy of being "deliberately slow" โ€” controlling computational complexity through the cost parameter. Cost=10 means 2^10 (1,024) rounds of key schedule iterations. On modern computers, bcrypt(cost=12) takes about 100โ€“300 milliseconds, while MD5 takes microseconds. This difference increases brute-force costs by orders of magnitude: MD5 GPU cracking ~10 billion/sec, bcrypt cost=12 GPU cracking ~100,000/sec โ€” about 100,000x difference.

bcrypt's Built-in Security Features

/* bcrypt output format */
$2b$12$[22-char salt][31-char hash]
  ^  ^  ^
  |  |  |-- 128-bit salt (22 Base64 chars)
  |  |-- cost factor (2^12 = 4096 iterations)
  |-- bcrypt version (2b = current standard)

Using bcrypt in Common Frameworks

// Node.js
const bcrypt = require('bcryptjs');

// Hash (async)
const hash = await bcrypt.hash(password, 12); // cost=12
// Store hash in database

// Verify
const isValid = await bcrypt.compare(password, storedHash);

// Python (Django)
from django.contrib.auth.hashers import make_password, check_password
hash = make_password(password)  # Uses PBKDF2 by default, can configure bcrypt
is_valid = check_password(password, hash)

# PHP
$hash = password_hash($password, PASSWORD_BCRYPT, ['cost' => 12]);
$isValid = password_verify($password, $hash);

# Go (golang.org/x/crypto/bcrypt)
hash, _ := bcrypt.GenerateFromPassword([]byte(password), 12)
err := bcrypt.CompareHashAndPassword(hash, []byte(password))

bcrypt's Limitations

bcrypt has a practical limitation: password length is capped at 72 bytes. Characters beyond 72 bytes (about 72 ASCII characters) don't affect the hash result. For systems supporting very long passwords, you can SHA256-hash the password first (compressing any length to 32 bytes), then use the SHA256 hash (as hex string or Base64) as bcrypt input.

2025 Recommendation: Prefer Argon2id

Although bcrypt is still considered secure, new projects in 2025 should prefer Argon2id โ€” the winner of the 2015 Password Hashing Competition. Compared to bcrypt, Argon2id adds a memory complexity parameter, making it more resistant to GPU parallel attacks, with no 72-byte length limit. OWASP lists Argon2id as the first choice in its password storage guidelines, with bcrypt as the alternative.

Migration Guide: From MD5 Passwords to bcrypt

If your system uses MD5 for password storage, here's a strategy for migrating to bcrypt: When a user successfully logs in (you have the plaintext password), replace the MD5 hash with a bcrypt hash; for long-inactive users, bcrypt the MD5 hash value itself first ("hash of hash"), then complete the full migration on their next login; add an identifier field in the database to record each user's password hash type.

Try the free tool now

Use Free Tool โ†’