How Does Bcrypt Password Hashing Actually Work?

bcrypt hashing — Chunky Munster

bcrypt hashing turns a password into a slow, salted hash so attackers cannot blast through guesses at machine speed. It is designed for password storage, not general-purpose data hashing, which is why try our free bcrypt generator is handy when you want to see the output format and parameters in a browser.

What bcrypt is actually doing

Bcrypt is a password hashing scheme built around one idea: make each guess expensive. When you hash a password with bcrypt, the algorithm combines the password with a random salt and runs an intentionally slow key setup process before producing the final hash.

The result is a single encoded string that usually contains several parts: the algorithm version, the cost factor, the salt, and the hash itself. That matters because the verifier does not need a separate database column for the salt or cost; they are embedded in the stored string.

Unlike fast hashes such as SHA-256 or MD5, bcrypt is supposed to be annoying. That slowness is a feature. If an attacker steals your password table, every extra millisecond per guess becomes a tax on brute-force attacks and credential stuffing.

Why the cost factor matters

The cost factor tells bcrypt how much work to do. In practice, raising the cost by one step roughly doubles the amount of computation needed to produce a hash, so the algorithm gets slower in a predictable way.

That tuning knob is the whole game. If your servers are stronger this year than they were last year, you can usually afford a higher cost without breaking logins. If you set it too low, hashes are cheap to crack. If you set it too high, every login becomes a tiny denial of service against your own app.

A sensible workflow is to benchmark on the hardware you actually deploy, then pick the highest cost that still keeps authentication responsive. You are balancing security against user experience, not chasing a magic number.

If you want to compare bcrypt with other password-specific schemes, our guide on hash algorithm choices is a useful contrast with the fast, non-password hashes people often misuse.

How salts stop rainbow table shortcuts

A salt is a random value added before hashing. Two users with the same password should not end up with the same hash, and salts make sure they do not.

Without salts, an attacker could precompute a giant lookup table for common passwords and reuse it across many leaks. With salts, the same password hashes differently for each account, which kills the economy of precomputed cracking.

Salts are not secret. They are stored alongside the hash and used again during verification. Their job is uniqueness, not hiding.

That is also why bcrypt is not just “hash the password once and call it done.” The random salt changes the input, while the cost factor changes the amount of work. Together they make bulk cracking much less attractive.

What happens during verification

bcrypt verification is simple once you have the stored hash. The application takes the password the user typed, reads the salt and cost from the saved bcrypt string, runs bcrypt again, and compares the new result to the stored one.

No decryption happens. There is no reverse function for passwords because bcrypt is one-way. Authentication succeeds only if the freshly computed hash matches the one in the database.

That makes password checking a compare workflow, not a retrieval workflow. The app never needs to know the plain-text password after it is entered, and that is exactly what you want.

A typical server-side flow in pseudocode looks like this:

storedHash = user.password_hash
inputPassword = request.body.password

if bcrypt_verify(inputPassword, storedHash) {
  allow_login()
} else {
  deny_login()
}

Notice what is missing: no manual salt handling, no custom string splitting, no home-grown crypto glue. The library should do the dangerous bits.

bcrypt versus fast hashes

Developers sometimes reach for MD5 or SHA-256 because they are familiar and easy to compute. That is fine for checksums, file integrity, or deduplication. It is a bad idea for passwords because speed helps attackers more than defenders.

Passwords are guessed offline after a breach. An attacker can throw GPUs and distributed rigs at fast hashes, then test billions of guesses per second. bcrypt slows that pipeline down, which makes weak passwords fail more slowly and gives defenders more breathing room.

In other words, speed is a liability when the thing you are protecting is human memory. You want a hash function that resists mass guessing, not one that preserves CPU cycles.

bcrypt is not the only password hash in town. Modern alternatives like scrypt and Argon2 add memory-hard design on top of CPU cost, which can make hardware cracking more painful. But bcrypt remains widely used, stable, and easy to deploy correctly.

Implementation details that actually matter

Most bcrypt libraries expose the same basic operations: generate a hash, verify a password, and sometimes check whether an existing hash needs rehashing with a higher cost. That last part is useful when you increase your security settings over time.

Here are the practical rules that usually matter in real apps:

And do not confuse password hashing with encryption. Encryption is meant to be reversed with a key. Password hashing is meant to be checked, not decrypted.

If you are inspecting password data in a migration or audit, do not try to parse it by hand unless you have to. The format is standardized enough for libraries to understand, but brittle enough that humans should avoid touching the internals unless there is a very specific reason.

See It in Action

Here is a realistic example of what changes when bcrypt hashing is applied to a password.

Before:
password = hunter2

After hashing:
$2b$12$Qx9mVb6q4FqF8t5g3n2L0eQ0N7xW7dK8mJ7t8w9zQ0lP1r2s3t4u

Same password, different hash if the salt changes:
$2b$12$Yt1pN8a2rS3dF4gH5jK6lOe9pQ1r2s3t4u5v6w7x8y9z0A1B2C3D4

The exact characters will differ from library to library and from run to run because the salt is random. That is the point. The hash is not supposed to look stable across accounts or across time.

Now compare verification:

stored = "$2b$12$Qx9mVb6q4FqF8t5g3n2L0eQ0N7xW7dK8mJ7t8w9zQ0lP1r2s3t4u"
user_input = "hunter2"

bcrypt_verify(user_input, stored)  # true
bcrypt_verify("hunter3", stored)  # false

This is the operational difference that matters: the app only needs to know whether the input matches the stored hash. It never needs the original password after hashing.

Frequently Asked Questions

Is bcrypt still secure in 2026?

Yes, bcrypt is still considered a solid password hashing option when used correctly with a reasonable cost factor. It is not the newest scheme, but it remains widely trusted and much better than fast hashes like MD5 or SHA-256 for password storage. The main rule is to keep the cost updated over time as hardware improves.

Should I store the bcrypt salt separately?

No. A bcrypt hash string already includes the salt, along with the cost and algorithm version. Storing it separately usually adds complexity without improving security.

Can I hash passwords with SHA-256 instead of bcrypt?

You can, but you should not for password storage. SHA-256 is designed to be fast, which makes brute-force attacks cheaper for an attacker. bcrypt deliberately slows verification down, which is the point.

What does the $2a$, $2b$, or $2y$ part mean?

That prefix identifies the bcrypt variant and version family. In practice, most modern libraries will generate and verify compatible bcrypt strings without you needing to manually edit that prefix. If you are migrating old data, library documentation matters more than guessing the format by eye.

The Bottom Line

bcrypt hashing works by combining a password with a random salt, running it through a slow computation, and storing the result in a format that can be verified later but not reversed. The salt blocks precomputed attacks, and the cost factor gives you a knob for slowing down brute-force attempts as hardware gets faster.

If you are building or auditing an authentication flow, the practical move is simple: use a trusted bcrypt library, set a sane cost, and store the full encoded hash exactly as returned. If you want to see the format without wiring up code, give the bcrypt generator a spin and compare a few outputs. It is a quick way to make the moving parts less mysterious.

// try the tool
try our free bcrypt generator →
// related reading
← all posts