🍱 Lunchbox Hands

security

How bcrypt Actually Works: Salts, Cost Factors & Why You Never MD5 a Password

A developer-friendly explanation of bcrypt — why password hashing is different from regular hashing, what the salt and cost factor in a $2b$ string mean, how deliberate slowness defeats brute-force attacks, and why MD5 and SHA-256 are the wrong tools for passwords.

Someone tells you to “hash the password before storing it,” so you run it through SHA-256 and feel responsible. Unfortunately, that’s almost as bad as storing it in plain text. Password hashing is a different problem from regular hashing, and bcrypt exists specifically to solve it. Here’s what’s going on inside that $2b$12$... string.

Why a normal hash is the wrong tool

General-purpose hashes — MD5, SHA-1, SHA-256 — are built to be fast. A modern GPU can compute billions of SHA-256 hashes per second. That speed is exactly what you want for checksums and file signatures, and exactly what you don’t want for passwords.

If an attacker steals your database of SHA-256 password hashes, they don’t try to “reverse” the hash. They guess: take a giant list of common passwords, hash each one, and compare. At billions of guesses per second, every weak or common password falls in minutes. Two more problems make it worse:

  • No salt → identical passwords produce identical hashes, so one cracked hash exposes every user who reused that password, and precomputed “rainbow tables” work instantly.
  • Speed → there’s no cost to guessing, so the attacker’s only limit is hardware.

Password hashing has to be deliberately slow and individually salted. That’s what bcrypt does.

What bcrypt does differently

bcrypt was designed in 1999 around three ideas that still hold up:

  1. A built-in random salt. Every hash gets a unique salt, generated for you and stored inside the output string. Identical passwords produce different hashes.
  2. A tunable cost factor. You choose how many rounds of work each hash takes. As hardware gets faster, you raise the number — the algorithm doesn’t go obsolete.
  3. Deliberate expense. It’s based on a modified Blowfish key-setup that resists GPU acceleration far better than a plain hash.

The result: verifying one password takes a few hundred milliseconds, which a user never notices. But an attacker trying to test billions of guesses now needs centuries of compute instead of minutes.

Reading a bcrypt string

A bcrypt hash isn’t just the digest — it’s a self-contained record. Here’s a real one, decoded:

$2b$12$R9h/cIPz0gi.URNNX3kh2OPST9/PgBkqquzi.Ss7KIUgO2t0jWMUW
 │  │  │└──────── 22-char salt ────────┘└──────── 31-char hash ────────┘
 │  │  └─ cost factor: 12  (2^12 = 4096 key-setup rounds)
 │  └──── minor version
 └─────── algorithm identifier: "2b" = bcrypt

Everything needed to verify a login is in this one string: the algorithm, the cost, the salt, and the digest. That’s why you store only this string — there’s no separate salt column to manage. When a user logs in, bcrypt reads the cost and salt back out of the stored hash, recomputes, and compares.

The cost factor is the whole game

The cost factor is a power of two — cost 12 means 2^12 = 4096 rounds. Each +1 doubles the work. So cost 13 is twice as slow as 12; cost 10 is four times faster than 12.

CostRelative workRough feel
8too fast — only for tests
10minimum for low-stakes apps
1216×a common 2026 default
1464×high-security, slower logins

Tune it to your hardware: pick the highest cost where a single hash still completes in roughly 200–500 ms on your production servers. You can feel the difference yourself with the bcrypt tool — bump the cost and watch the time climb.

bcrypt’s one quirk: the 72-byte limit

bcrypt only looks at the first 72 bytes of the input. Anything past that is silently ignored — so a 100-character passphrase and its first 72 characters hash identically. For normal passwords this never matters, but it’s why some systems pre-hash the password (e.g. SHA-256 it first, then bcrypt the result) before feeding it in. If you do that, be deliberate about it; don’t discover it by accident.

bcrypt vs argon2 vs scrypt

bcrypt is excellent and still a perfectly responsible choice in 2026. Two newer algorithms go further by also being memory-hard — they require lots of RAM per guess, which blunts the cheap parallel hardware attackers love:

  • scrypt — memory-hard, widely available.
  • argon2id — the current recommendation from most security bodies; memory- and compute-tunable.

If you’re starting fresh and your platform supports it, argon2id is the modern first pick. If you’re already on bcrypt with a sensible cost, you don’t need to rush a migration — bcrypt remains far stronger than any plain hash.

The rules to actually remember

  • Never use MD5, SHA-1, or SHA-256 alone for passwords. Those belong in the hash generator for checksums, not auth.
  • Always use a purpose-built password hash — bcrypt, scrypt, or argon2id — with a per-user salt (bcrypt and argon2 handle the salt for you).
  • Tune the cost so one hash takes a few hundred milliseconds, and raise it over time.
  • Encourage strong passwords so even a slow hash has a large space to defend — a password strength checker and a real password generator help users get there.

Password hashing isn’t about hiding the password — it’s about making each guess expensive. bcrypt’s entire design is “be slow on purpose,” and that’s exactly the property you want.