Fix Password Hashing & Bcrypt Verification Errors
Debug password hashing failures: bcrypt cost factor issues, salt generation problems, hash comparison errors, and migration from MD5/SHA to bcrypt.
Password hashing errors can lock users out of their accounts or, worse, leave passwords vulnerable to attack. This guide covers common password hashing pitfalls, from bcrypt cost factor issues to timing-safe comparison failures.
Common errors covered
Comparing plaintext password to hash (always fails)
Login always fails even with correct password
password !== storedHash is always true
A common beginner mistake: comparing the plain password string directly to the bcrypt hash string. You must use the bcrypt verify function, which hashes the input with the same salt and compares the results.
Step-by-step fix
-
1
Never use
===or==to compare a password to a hash. -
2
Use the bcrypt library compare function:
bcrypt.compare(password, hash). - 3 Generate a test hash with our Hash Generator to understand the format.
-
4
Verify your database stores the full bcrypt hash (starts with
$2b$).
// WRONG: direct string comparison
if (password === storedHash) {
// This NEVER works
}
// CORRECT: use bcrypt.compare()
const isValid = await bcrypt.compare(password, storedHash);
if (isValid) {
// Password matches!
}
Bcrypt cost factor too high (hashing takes too long)
Request timeout: password hashing took > 30 seconds
Server: 503 Service Unavailable during login
Bcrypt cost factor is exponential: cost 10 is about 100ms, cost 12 is about 400ms, cost 15 is about 3.5s, cost 20 is about 2 minutes. A cost factor that is too high causes request timeouts.
Step-by-step fix
- 1 Check your current bcrypt cost factor (rounds).
- 2 Use cost factor 10-12 for web applications (100-400ms per hash).
- 3 Run a benchmark: time how long hashing takes on your production hardware.
- 4 Use our Password Generator to create strong passwords that compensate for a lower cost factor.
// Cost factor 18 = ~45 seconds per hash! const hash = await bcrypt.hash(password, 18);
// Cost factor 12 = ~400ms - good balance const hash = await bcrypt.hash(password, 12);
Timing-unsafe hash comparison
Security audit: hash comparison is vulnerable to timing attacks
Using simple string comparison for hash verification leaks timing information. An attacker can determine how many characters match based on response time, gradually guessing the hash.
Step-by-step fix
-
1
Replace
===with a constant-time comparison function. -
2
In Node.js: use
crypto.timingSafeEqual(). -
3
In Python: use
hmac.compare_digest(). - 4 For bcrypt: the library compare function is already timing-safe.
// Timing-unsafe comparison
if (computedHash === storedHash) { ... }
// Timing-safe comparison
const a = Buffer.from(computedHash);
const b = Buffer.from(storedHash);
if (crypto.timingSafeEqual(a, b)) { ... }
Prevention Tips
- Always use bcrypt, scrypt, or Argon2 for password hashing - never MD5 or SHA.
- Use the bcrypt library built-in compare function instead of manual comparison.
- Benchmark your cost factor on production hardware - aim for 100-400ms per hash.
- Generate strong passwords with our Password Generator.
Frequently Asked Questions
Is SHA-256 safe for password hashing?
No. SHA-256 is a fast hash designed for data integrity, not password hashing. It can be brute-forced at billions of hashes per second. Use bcrypt, scrypt, or Argon2 which are deliberately slow.
How do I migrate from MD5 to bcrypt?
On next login: verify the old MD5 hash, then re-hash the plaintext password with bcrypt. Store the new hash. Add a flag to track which algorithm each user hash uses.
What password length should I require?
Minimum 8 characters, but 12+ is recommended. Do not set a maximum below 128 characters. Focus on preventing common passwords (use a blocklist) rather than complex character requirements.
Related Error Guides
Related Tools
Still stuck? Try our free tools
All tools run in your browser, no signup required.