Generate and verify secure Bcrypt password hashes with customizable security parameters
Bcrypt is a password-hashing function designed specifically for securely storing passwords. Created in 1999 by Niels Provos and David Mazières, bcrypt remains one of the most recommended methods for password hashing today, despite its age.
Bcrypt is based on the Blowfish cipher and incorporates several security features that make it ideal for password hashing:
Bcrypt uses a cost parameter that determines how computationally intensive the hashing process will be. This parameter allows you to make hashing slower as computers get faster, maintaining security over time. The cost is expressed as a power of 2, so each increment doubles the required work.
Bcrypt automatically generates a random salt for each password hash. This 128-bit (22 character) salt ensures that identical passwords will produce different hash outputs, thwarting rainbow table attacks and ensuring each hash is unique.
Bcrypt uses an expensive key setup phase derived from the Blowfish cipher. This makes it significantly slower than algorithms like MD5 or SHA-1, which is beneficial for password hashing where slowness is a security feature.
A bcrypt hash string consists of several parts:
$2a$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWy |_||_||____________________||______________________________| | | | | | | | +-- Hash (Base64-encoded) | | +-- Salt (Base64-encoded, 22 chars) | +-- Cost parameter (10) +-- Algorithm version (2a)
The algorithm version identifier has several variations:
$2$
- Original version (now deprecated)$2a$
- Most widely used format$2y$
- PHP's implementation (functionally identical to $2a$ in modern PHP)$2b$
- Fixed implementation handling non-ASCII passwords correctlyThe cost parameter (10 in the example) indicates the work factor - 2^10 iterations. Higher values increase security but also increase computation time. As hardware improves, this value should be increased to maintain security.
Cost Factor | Iterations (2^cost) | Approx. Time (2023 hardware) | Recommended Use |
---|---|---|---|
4 | 16 | < 1ms | Testing only (insecure) |
8 | 256 | ~10ms | Minimum acceptable |
10 | 1,024 | ~100ms | Standard recommendation |
12 | 4,096 | ~400ms | More secure |
14 | 16,384 | ~1.6s | High-security (may affect user experience) |
16 | 65,536 | ~6s | Very high security (noticeable delay) |
The optimal cost factor balances security with performance. The general guideline is to choose a factor that makes hashing take about 250ms on your server hardware. This is typically around 10-12 in 2023.
For high-security applications or when processing fewer authentication requests, you might choose a higher cost. For applications with high authentication volume, you might need a lower cost to maintain performance.
Algorithm | Year | Memory Usage | Parallelization Resistant | Recommended Use |
---|---|---|---|---|
MD5 | 1992 | Low | No | Not for passwords (broken) |
SHA-256 | 2001 | Low | No | Not for passwords (too fast) |
Bcrypt | 1999 | Low (4KB) | Yes | Good general purpose |
PBKDF2 | 2000 | Low | No | FIPS compliance requirements |
Scrypt | 2009 | High (configurable) | Yes | Memory-hard requirements |
Argon2 | 2015 | High (configurable) | Yes | Strongest current recommendation |
// Generate a hash (PHP 5.5+) $hash = password_hash('user_password', PASSWORD_BCRYPT, ['cost' => 12]); // Verify a password against a hash if (password_verify('user_password', $hash)) { echo "Password is valid!"; } else { echo "Password is invalid!"; }
const bcrypt = require('bcrypt'); const saltRounds = 12; // Generate a hash async function hashPassword(password) { const hash = await bcrypt.hash(password, saltRounds); return hash; } // Verify a password async function verifyPassword(password, hash) { const match = await bcrypt.compare(password, hash); return match; }
import bcrypt # Generate a hash def hash_password(password): password_bytes = password.encode('utf-8') salt = bcrypt.gensalt(12) # rounds=12 hash = bcrypt.hashpw(password_bytes, salt) return hash # Verify a password def verify_password(password, hashed): password_bytes = password.encode('utf-8') return bcrypt.checkpw(password_bytes, hashed)
Select a cost factor that makes hashing take about 250ms on your server. This balances security with performance. Periodically review and increase this value as hardware improves.
Bcrypt has a 72-byte limit for passwords. For extremely long passwords, consider pre-hashing with SHA-256 before passing to bcrypt, but be careful to maintain salt uniqueness.
Even though bcrypt hashes are designed to be resistant to cracking, you should still protect your hash database as if it contained plaintext passwords. Treat hash leaks as serious security incidents.
If you're migrating from a weaker algorithm, rehash passwords with bcrypt the next time users log in successfully.
While bcrypt is still secure, consider Argon2 for new projects, especially where memory-hardness is a concern. Argon2 won the Password Hashing Competition in 2015 and provides additional protection against certain types of attacks.
Yes, bcrypt remains secure when used with an appropriate cost factor. No practical attacks have been demonstrated against properly implemented bcrypt. Its adaptable work factor allows it to remain secure as hardware improves.
A good rule of thumb is to reevaluate your cost factor every 1-2 years, or whenever you significantly upgrade your hardware. The goal is to maintain the same time cost (around 250ms) as hardware gets faster.
When a user logs in, verify their password against your old hash system. If correct, create a new bcrypt hash of their password and replace the old hash. This gradual migration approach doesn't require users to reset passwords.
For example:
if (old_hash_verify(password, stored_old_hash)) { // Login successful, update to bcrypt $new_bcrypt_hash = password_hash(password, PASSWORD_BCRYPT, ['cost' => 12]); // Store $new_bcrypt_hash in database, replacing old hash }
These prefix identifiers represent different implementations or fixes in bcrypt:
For modern applications, these differences rarely matter as current libraries handle them correctly.
Despite being over two decades old, bcrypt remains one of the most reliable ways to hash passwords. Its adaptive nature, built-in salting, and deliberate slowness provide excellent protection against brute force attacks and rainbow tables.
While newer algorithms like Argon2 offer additional benefits (particularly better memory-hardness), bcrypt continues to be a solid choice that's widely supported across languages and platforms. By selecting an appropriate cost factor and following best practices, you can ensure your users' passwords remain secure against attackers.