Skip to content

bcrypt Explained: How Adaptive Password Hashing Works

How bcrypt works — the Eksblowfish key schedule, cost factor, salt, and the $2b$ hash format — why slow adaptive hashing protects passwords, and bcrypt's limits.

Published on 8 min read

When a database leaks, the attacker's next move is to crack the stored password hashes offline. The single most important defense is choosing a hash function that is deliberately slow. bcrypt, designed in 1999, was one of the first algorithms built specifically for this job — and more than two decades later it is still everywhere, from Linux logins to web framework defaults. This article explains how bcrypt actually works under the hood, why its adaptive cost factor matters, and where its limits lie.

Why fast hashes are the wrong tool for passwords

General-purpose cryptographic hashes like MD5, SHA-1, and SHA-256 are engineered to be fast. That is a virtue for verifying file integrity or signing data, but it is a fatal weakness for passwords. A modern GPU can compute billions of SHA-256 hashes per second, and dedicated ASIC rigs go far higher. If your database stores SHA-256(password), an attacker who steals it can test enormous candidate lists in hours.

Two problems compound the speed issue:

  • Rainbow tables. Without a per-user salt, identical passwords produce identical hashes, and precomputed lookup tables turn "cracking" into a table lookup.
  • GPU brute force. Even with a salt, a fast hash lets attackers try dictionaries and mask patterns at staggering throughput.

The fix is not a "more secure" hash — SHA-256 is cryptographically sound — but a slow, salted, adaptive one. For the broader picture of how hash functions work, see the pillar guide on how hashing works, and for the practical decision framework, the password hashing guide.

Where bcrypt came from

bcrypt was introduced by Niels Provos and David Mazières in their 1999 USENIX paper "A Future-Adaptable Password Scheme." Their key insight was future-adaptability: hardware gets faster every year, so a password hash should expose a tunable parameter that lets defenders keep increasing the work as machines improve, without changing the algorithm.

bcrypt builds on the Blowfish block cipher (also designed by Bruce Schneier). Blowfish is unusual in that its key setup is intentionally expensive: it derives a large set of subkeys and S-boxes from the key through many rounds. bcrypt exploits and amplifies exactly this property.

The Eksblowfish key schedule

The heart of bcrypt is a modified Blowfish setup called Eksblowfish — "expensive key schedule Blowfish." Standard Blowfish keying runs once. Eksblowfish instead runs the key-expansion step repeatedly, mixing in both the salt and the password each iteration.

The algorithm proceeds roughly like this:

  1. Initialize the Blowfish P-array and S-boxes from the constant digits of pi.
  2. Run EksBlowfishSetup once, alternately mixing in the salt and the password.
  3. Repeat the key-expansion step 2^cost times, re-keying alternately with the salt and the password.
  4. Use the resulting cipher state to encrypt the constant string "OrpheanBeholderScryDoubt" 64 times.
  5. The final 192-bit ciphertext is the password hash.

Because step 3 loops 2^cost times, the cost factor controls how slow the whole operation is. Crucially, the work happens in the key schedule, which constantly reads and writes the 4 KB of S-box state at pseudo-random offsets. That frequent, small, data-dependent memory access is awkward for GPUs and ASICs to parallelize efficiently — one reason bcrypt has aged better than fast hashes against specialized hardware.

The cost (work) factor

The cost factor is a single integer, typically between 4 and 31. Each increment doubles the work, because the inner loop runs 2^cost times:

  • cost 10 → 1,024 key-expansion rounds
  • cost 11 → 2,048 rounds
  • cost 12 → 4,096 rounds

This is what "future-adaptable" means in practice. When you provision a server today, you pick a cost that takes a comfortable fraction of a second per hash on your hardware. As CPUs get faster over the years, you bump the cost by one to keep the same wall-clock cost. Existing hashes keep verifying because the cost is stored inside the hash string — you can rehash a user's password at the new cost the next time they log in.

A common target in 2026 is a cost that yields roughly 200–400 ms per hash on your production hardware; for many servers that lands around cost 12–14. Measure it on your own machines rather than copying a number, and avoid going so high that authentication becomes a denial-of-service vector.

The 128-bit salt

bcrypt generates a 128-bit (16-byte) random salt for every password and stores it as part of the hash. The salt ensures that two users with the same password get completely different hashes, which defeats rainbow tables and precomputation entirely — an attacker must attack each hash individually.

You do not manage the salt yourself; it is generated, embedded, and parsed automatically by any correct bcrypt implementation. Never reuse a salt and never derive it from something predictable like a username.

The 72-byte limit and the null-byte caveat

bcrypt has a well-known input limitation: it only considers the first 72 bytes of the password. Anything beyond byte 72 is silently ignored. For typical passwords this is irrelevant, but it matters in two cases:

  • Long passphrases or pre-hashed input. If you pass very long inputs (or base64-encoded data), bytes past 72 contribute nothing to the hash.
  • The null-byte truncation bug. Some older bcrypt implementations, due to their C-string handling, truncate the password at the first NUL (0x00) byte. A password like secret\0morestuff could be treated as just secret. Modern, well-maintained libraries handle this correctly, but it is a historical footgun worth knowing.

A widely used mitigation when you need to support arbitrarily long inputs is to pre-hash the password with SHA-256 or SHA-512 and then base64-encode the digest before feeding it to bcrypt — but be careful that the base64 output stays within 72 bytes and contains no null bytes. Many teams instead simply move to Argon2 for new systems.

Anatomy of a bcrypt hash string

bcrypt encodes everything needed to verify a password into a single self-describing string using the modular crypt format. Here is a $2b$ hash with its parts labeled:

$2b$12$R9h/cIPz0gi.URNNX3kh2OPST9/PgBkqquzi.Ss7KIUgO2t0jWMUW
└┬┘└┬┘ └────────────────────┬─────────────────────────────┘
 │   │      └─ 22-char base64 salt + 31-char base64 hash
 │   └─ cost factor (12)
 └─ algorithm version identifier ($2b$)

Breaking it down:

  • $2b$ — the version identifier. The original prefix was $2$, later $2a$; $2x$ and $2y$ flagged implementations affected by the null-byte/sign-extension bugs; $2b$ is the current, correct version. You will sometimes still see $2a$ and $2y$ in the wild.
  • 12 — the cost factor, as a two-digit decimal.
  • The next 22 characters — the 128-bit salt, encoded in bcrypt's own base64 alphabet.
  • The final 31 characters — the 184 bits of the actual hash output (the truncated Eksblowfish ciphertext), in the same base64 encoding.

Note that bcrypt uses a non-standard base64 alphabet (./ instead of +/, different ordering), so do not try to decode it with a generic base64 decoder.

Security properties — and the memory-hardness gap

bcrypt's strengths hold up well:

  • Per-user salts make rainbow tables and cross-account precomputation useless.
  • Adjustable cost lets you raise the difficulty over time.
  • Small-memory random access in the key schedule gives it decent resistance to GPU and ASIC acceleration compared with plain SHA hashing.

But there is a real limitation. bcrypt uses only about 4 KB of memory, which fits comfortably in fast on-chip cache. Because it is not memory-hard, an attacker can place thousands of bcrypt cores on a single FPGA or ASIC, since memory bandwidth is not the bottleneck. Newer algorithms were designed specifically to close this gap by forcing large memory use, making massively parallel cracking expensive. The leading modern successor is Argon2, which won the Password Hashing Competition and adds tunable memory and parallelism on top of a cost factor. The earlier PBKDF2 and scrypt designs sit between these eras — scrypt being the first widely adopted memory-hard scheme.

Using bcrypt correctly

A few operational rules matter as much as the algorithm choice:

  • Choose a cost factor empirically. Benchmark on your real hardware; aim for a few hundred milliseconds per hash and revisit it periodically.
  • Let the library handle salts. Use a maintained library's hash and verify functions; never roll your own salt management.
  • Always verify in constant time. Compare the stored hash against the recomputed one using the library's verify/compare function, which is constant-time. A naive == string comparison can leak timing information.
  • Rehash on login when policy changes. When you raise the cost (or migrate algorithms), transparently upgrade each user's stored hash the next time they authenticate successfully.

Should you use bcrypt in 2026?

bcrypt remains acceptable and is vastly better than any fast cryptographic hash for passwords. If you already run bcrypt at a sensible cost factor, you are in reasonable shape and do not need to panic-migrate. For new systems, Argon2id is the preferred choice because its memory-hardness raises the cost of large-scale parallel cracking that bcrypt cannot resist as well. PBKDF2 stays relevant mainly where a FIPS-validated primitive is mandated.

Conclusion

bcrypt's enduring value comes from one elegant idea: make the password hash deliberately, adjustably slow by abusing Blowfish's expensive key setup, and bake the salt and cost directly into the output string. Understanding the $2b$ format, the cost factor, the 128-bit salt, and the 72-byte limit lets you deploy it correctly and reason about its tradeoffs against newer, memory-hard designs.

Want to see it in action? You can generate a bcrypt hash in your browser with an adjustable cost factor. Everything runs client-side via WebAssembly — nothing you type is ever uploaded.

Related articles

A practical guide to storing passwords securely in 2026 — why general hashes fail, salts vs peppers, work factors, and choosing Argon2id, scrypt, bcrypt or PBKDF2.
How Argon2 works — memory-hard password hashing, the Argon2d/i/id variants, the memory, time and parallelism parameters, and why it beats bcrypt and PBKDF2.
How PBKDF2 and scrypt work — iterated HMAC, salts and iteration counts, scrypt's memory-hard ROMix — and when to use each key derivation function for passwords and keys.