Storing passwords in plain text is malpractice. Everyone knows that now. But there's a second mistake that's almost as common: hashing passwords with something fast like MD5 or SHA-256. Those algorithms are built for speed, and speed is exactly what you don't want when an attacker gets hold of your database and starts brute-forcing.
Bcrypt solves that problem by design. It's slow on purpose, and it's been the go-to password hashing function for over two decades. Here's what you actually need to know to use it correctly.
What bcrypt actually is
Bcrypt is a password hashing function based on the Blowfish cipher, published back in 1999. Two things make it well-suited for passwords:
- It's deliberately slow. A general-purpose hash like SHA-256 can compute billions of hashes per second on modern hardware. Bcrypt is tuned so a single hash takes tens or hundreds of milliseconds — trivial for one login, painful for an attacker running through millions of guesses.
- It has a built-in cost factor. You control how slow it is via a "work factor" (also called rounds or cost). As hardware gets faster, you bump the cost up. The algorithm ages gracefully.
It also handles salting for you. Every bcrypt hash includes a random salt baked into the output, so two identical passwords produce different hashes. You don't manage salts separately, which removes a whole category of bugs.
Anatomy of a bcrypt hash
A bcrypt hash looks like this:
$2b$12$eImiTXuWVxfM37uY4JANjQ.hE1qJm3z8xN.MFOSA6bYnkGkJ8ZC5mBroken down:
$2b$— the algorithm version identifier.12— the cost factor (2^12 iterations).- The next 22 characters — the salt.
- The remainder — the actual hash.
Everything needed to verify a password is in that one string. You store the whole thing in a single column and you're done.
When to use it (and when not to)
Reach for bcrypt whenever you're storing user passwords in a web or mobile app. It's a safe, boring default, and boring is what you want for security.
There are two situations where you might pick something else:
- Very high-security or high-throughput systems. Argon2 (specifically Argon2id) won the Password Hashing Competition and is memory-hard, which makes GPU and ASIC attacks harder. If your framework supports it well, it's a great choice. Bcrypt is still perfectly acceptable, though.
- Anything that isn't a password. Don't use bcrypt for hashing files, API tokens, or general data integrity. For those, use SHA-256 or similar. Bcrypt is a password tool.
One quiet limitation: bcrypt only looks at the first 72 bytes of input. Passwords longer than that get silently truncated. In practice this rarely matters, but if you allow very long passphrases, be aware of it — or pre-hash the password with SHA-256 and base64-encode the result before feeding it to bcrypt.
Common gotchas
Don't hash on the client
Hashing must happen on the server. If you hash in the browser and send the hash, that hash effectively becomes the password — an attacker who steals it can replay it directly.
Never write the comparison yourself
Always use the library's built-in verify function. It does a constant-time comparison and re-parses the cost and salt from the stored hash. Rolling your own comparison invites timing attacks and subtle mismatches.
Pick a sane cost factor
A cost of 10 was standard years ago; 12 is a reasonable modern default. The right number depends on your hardware — aim for roughly 200–300ms per hash on your production servers. Too low and it's cheap to brute-force; too high and your login endpoint becomes a denial-of-service target against yourself.
Plan for upgrades
Because the cost is stored in the hash, you can transparently upgrade users. On successful login, check whether the stored cost is below your current target and, if so, re-hash the plaintext password (which you have in that moment) and save the new hash.
A worked example
Here's the typical flow in Node.js using the bcrypt package. The same pattern applies in Python (bcrypt), PHP (password_hash), Ruby, and Go.
const bcrypt = require('bcrypt');
const COST = 12;
// On signup: hash the password before storing
async function hashPassword(plain) {
return bcrypt.hash(plain, COST);
}
// On login: compare the submitted password against the stored hash
async function verifyPassword(plain, storedHash) {
return bcrypt.compare(plain, storedHash);
}
// Example usage
(async () => {
const hash = await hashPassword('correct horse battery staple');
console.log(hash);
// $2b$12$... store this whole string in your DB
const ok = await bcrypt.compare('correct horse battery staple', hash);
console.log(ok); // true
const bad = await bcrypt.compare('wrong password', hash);
console.log(bad); // false
})();Notice there's no separate salt handling and no manual comparison logic. You call hash on the way in and compare on the way out. That's the whole API surface for the common case.
The upgrade-on-login pattern looks like this:
async function login(plain, user) {
const ok = await bcrypt.compare(plain, user.passwordHash);
if (!ok) return false;
// Re-hash if the stored cost is below our current target
const rounds = bcrypt.getRounds(user.passwordHash);
if (rounds < COST) {
user.passwordHash = await bcrypt.hash(plain, COST);
await user.save();
}
return true;
}Trying it out quickly
If you just want to see what a bcrypt hash looks like, experiment with different cost factors, or verify a hash you found in a database, you don't need to spin up a project. Our Bcrypt Hash Generator lets you generate and inspect hashes right in the browser — handy for debugging or sanity-checking your cost settings before you commit to a number in production.
The short version
- Use bcrypt (or Argon2id) for passwords. Never MD5, SHA-1, or plain SHA-256.
- Let the library handle salting and comparison.
- Set a cost factor that takes a couple hundred milliseconds on your hardware.
- Watch the 72-byte input limit if you allow long passphrases.
- Re-hash on login when you raise the cost.
Password hashing is one of those areas where the right answer has been stable for years. Pick a well-maintained library, use its default helpers, and resist the urge to be clever. Boring code keeps your users safe.