Every data breach that exposes "hashed" passwords should be a reminder that not all hashes are created equal. Storing passwords with MD5 or SHA-256 is essentially storing them in plain text — the only thing standing between a breach and a full account takeover is the speed at which an attacker can run hashes. BCrypt's entire design philosophy is to make that speed a non-issue.
Try it yourself with the DevToolShack BCrypt Hash Generator — generate and verify bcrypt hashes instantly, free in your browser.
Why Fast Hashing Algorithms Are Wrong for Passwords
SHA-256 was designed to be fast. Modern GPUs can compute billions of SHA-256 hashes per second. This is great for cryptographic applications like signing certificates or verifying file integrity — but catastrophic for password storage.
Here's what that means in practice: if your database is breached and passwords are stored as SHA-256 hashes, an attacker with consumer GPU hardware can crack common passwords in seconds and most passwords within days. The entire password database becomes readable shortly after the breach.
How BCrypt Works
BCrypt was designed in 1999 specifically for password hashing. It has three key properties that make it suitable where SHA-256 is not:
1. It's Intentionally Slow
BCrypt uses a configurable cost factor (also called work factor or rounds). A cost factor of 10 means the algorithm performs 2¹⁰ = 1,024 iterations internally. Cost factor 12 means 2¹² = 4,096 iterations. Each iteration increase roughly doubles the time to hash — and doubles the time for an attacker to crack.
2. It Includes a Salt Automatically
BCrypt generates a unique random salt for every password hash. The salt is embedded in the output string — you don't need to store it separately. This means two identical passwords produce completely different hashes, which defeats precomputed rainbow table attacks.
3. The Output Is Self-Contained
A BCrypt hash looks like this:
$2b$12$LQv3c1yqBWVHxkd0LHAkCOYz6TtxMQJqhN8/LewdBPj/Ur2aVvx5G
Breaking this down:
$2b$— BCrypt version identifier12$— cost factor (12 in this case)- Next 22 characters — the random salt
- Remaining characters — the hash
Everything needed to verify a password is in that one string. No separate salt column needed in your database.
Choosing a Cost Factor
The right cost factor is one that makes hashing take approximately 100–300ms on your hardware. This is fast enough that users don't notice during login, but slow enough that brute-force attacks become impractical.
| Cost Factor | Approx. time (modern server) | Recommendation |
|---|---|---|
| 10 | ~10ms | Minimum acceptable |
| 12 | ~40ms | Good default for most apps |
| 14 | ~160ms | High-security applications |
| 16 | ~640ms | Very high security (slow UX) |
As hardware gets faster over time, you should increase your cost factor. BCrypt lets you re-hash passwords at login with a higher cost factor without invalidating existing hashes.
Using BCrypt in Code
// Node.js — bcrypt library
import bcrypt from 'bcrypt';
const COST_FACTOR = 12;
// Hashing a password (at registration)
async function hashPassword(plaintext) {
return await bcrypt.hash(plaintext, COST_FACTOR);
}
// Verifying a password (at login)
async function verifyPassword(plaintext, hash) {
return await bcrypt.compare(plaintext, hash);
}
// Usage
const hash = await hashPassword('user-password');
// Store hash in database — never store the plaintext
const isValid = await verifyPassword('user-password', hash);
// true — use this to grant access
# Python — bcrypt library
import bcrypt
COST_FACTOR = 12
# Hashing (at registration)
def hash_password(plaintext: str) -> bytes:
return bcrypt.hashpw(plaintext.encode(), bcrypt.gensalt(rounds=COST_FACTOR))
# Verifying (at login)
def verify_password(plaintext: str, hashed: bytes) -> bool:
return bcrypt.checkpw(plaintext.encode(), hashed)
BCrypt vs Argon2
Argon2 is the winner of the 2015 Password Hashing Competition and is considered the modern successor to BCrypt. The key difference: Argon2 is also memory-hard — it requires a configurable amount of RAM in addition to CPU time. This makes GPU-based cracking even more difficult, since GPUs have limited memory bandwidth per core.
| Feature | BCrypt | Argon2 |
|---|---|---|
| Designed | 1999 | 2015 |
| Memory-hard | No | Yes |
| Configurable cost | Yes (iterations) | Yes (time + memory) |
| Widely supported | Excellent | Good, growing |
| Use today | Yes — still solid | Preferred for new projects |
For new projects, Argon2id (a hybrid variant) is the current best practice. For existing BCrypt implementations, there's no urgent need to migrate — BCrypt remains secure when used with a sufficient cost factor.