Verify a Password Against a Bcrypt Hash
Learn how to verify a password against a stored bcrypt hash. Understand the constant-time comparison process, why you cannot decrypt a hash, and common verification pitfalls to avoid.
Detailed Explanation
Verifying a Password Against a Bcrypt Hash
Password verification with bcrypt works by re-hashing the candidate password using the salt extracted from the stored hash, then comparing the result. You never "decrypt" a bcrypt hash — the process is one-way.
How Verification Works
- User submits their password (e.g.,
"mypassword123") - Retrieve the stored hash from the database (e.g.,
"$2b$12$LJ3m4ys...") - Extract the salt and cost factor from the stored hash
- Hash the candidate password using the extracted salt and cost factor
- Compare the newly computed hash with the stored hash
- If they match, the password is correct
Code Examples
Node.js:
const bcrypt = require('bcrypt');
// Hashing (during registration)
const hash = await bcrypt.hash('mypassword123', 12);
// Store hash in database
// Verification (during login)
const isMatch = await bcrypt.compare('mypassword123', hash);
// isMatch === true
Python:
import bcrypt
# Hashing
hashed = bcrypt.hashpw(b'mypassword123', bcrypt.gensalt(rounds=12))
# Verification
is_match = bcrypt.checkpw(b'mypassword123', hashed)
# is_match == True
Constant-Time Comparison
Bcrypt libraries use constant-time comparison to prevent timing attacks. A naive string comparison (hash1 === hash2) returns false as soon as it finds the first mismatched character, leaking information about how many characters matched. Constant-time comparison always takes the same amount of time regardless of where the mismatch occurs.
Common Pitfalls
- Comparing hashes directly — never compare two bcrypt hashes with
===. Always use the library’s compare function, which handles salt extraction and constant-time comparison - Encoding issues — ensure the password is encoded consistently (UTF-8) during both hashing and verification
- Truncation — bcrypt silently truncates passwords beyond 72 bytes. If you hash a 100-byte password, only the first 72 bytes are used
- Null bytes — some implementations truncate at null bytes. Avoid accepting null bytes in passwords
- Async vs sync — in Node.js, prefer
bcrypt.compare()(async) overbcrypt.compareSync()to avoid blocking the event loop
What Happens on Failure
When verification fails, the library simply returns false. It does not indicate whether the cost factor, salt, or hash portion mismatched — this is intentional to prevent information leakage.
Use Case
Password verification is the most common bcrypt operation in any application with user authentication. Understanding the verification process helps developers avoid subtle bugs like encoding mismatches, truncation issues, and timing vulnerabilities. It is especially important when migrating between bcrypt libraries or across programming languages, where differences in API design can introduce verification failures.