What is bcrypt?
bcrypt is a password-hashing function designed by Niels Provos and David Mazières in 1999, presented at USENIX and adopted as the default password hashing scheme for OpenBSD. It was built to solve a specific problem that MD5 and SHA-based schemes did not: making the hash expensive to compute. Where MD5 hashes billions of values per second on modern GPUs, a single bcrypt hash at a reasonable cost factor takes a fraction of a second of CPU time on a single core — by design.
bcrypt is not a general-purpose hash function. It is a key-derivation function (KDF) built on top
of the Blowfish cipher, deliberately reusing Blowfish's expensive key schedule as the
computational bottleneck. Every bcrypt output includes a random 16-byte salt, the cost factor, and
the derived key, all encoded in a single 60-character string that self-describes its own parameters.
That encoding — $2b$<cost>$<salt>$<hash> — is one of the reasons
bcrypt has survived two decades of operational use: a system that only needs to store a string can
verify passwords without any separate metadata.
How bcrypt works, step by step
1. The EksBlowfish key schedule
Blowfish initializes its sixteen 18-entry P-array and four 256-entry S-boxes from the digits of
π, then encrypts the P-array entries using a key. bcrypt's variant — called eksblowfish
— does something different: it initializes the P-array and S-boxes from π, then iteratively
encrypts the P-array using the salt and password in alternating passes. This is the expensive
part: the key schedule is re-run 2cost times, where the cost is stored
right in the output string.
2. Encrypting the magic string
After the key schedule completes, bcrypt encrypts the static string
"OrpheanBeholderScryDoubt" (sixteen bytes of known plaintext) three times with the
derived key, producing a 24-byte value that is then compressed into the final 31-character hash
portion of the output.
3. Output encoding
The full 60-character string breaks down as follows:
$2b$ 10$ InnoU1I9E53pJR7Ckm3pEuw6rFE2yXXIBJHbp8VDcUli4ySKsEqRu 2b— the bcrypt version (2a was the original; 2b is the current standard)10— the cost factor (encoded as a two-character integer)InnoU1I9E53pJR7Ckm3pEu— the 22-character Base64 encoding of the 16-byte saltw6rFE2yXXIBJHbp8VDcUli4ySKsEqRu— the 31-character encoding of the derived key
The entire hash is self-contained: the salt and cost factor needed to verify a password are baked into the string itself, which means a system storing bcrypt hashes needs no additional salt columns, no configuration, and no versioning scheme.
The 72-byte limit every developer should know
bcrypt internally treats the password as a key for the Blowfish cipher, which has a maximum key length of 72 bytes. When a password exceeds 72 bytes, every byte after the 72nd is silently discarded. This page detects and warns you when this happens, but the real fix is upstream: long passwords should be truncated or hashed down to 72 bytes before they reach bcrypt.
A common pattern in legacy systems is to run SHA-256(password) first — producing
a 32-byte fixed-length string — and then bcrypt that output. This removes the 72-byte limitation
and guarantees every password contributes at least 256 bits of entropy. The tradeoff is that
the SHA-256 step is its own collision surface, but since passwords are typically low-entropy
strings, the practical risk is minimal compared to the alternative of silent truncation.
Choosing the right cost factor
The cost factor determines how many times the key schedule iterates: cost n means
2n rounds. Each increment doubles the computational cost. On most modern
hardware the guideline is to pick the highest cost factor that keeps verification under about 250
milliseconds for a single login attempt.
| Cost | Rounds | Approx. cost | Typical time (2024 desktop) |
|---|---|---|---|
| 8 | 256 | Low | ~15–25 ms |
| 10 | 1,024 | Medium (OWASP minimum) | ~60–100 ms |
| 12 | 4,096 | High | ~250–400 ms |
| 14 | 16,384 | Very high | ~1–2 s |
| 16 | 65,536 | Extreme | ~4–8 s |
Cost 10 is the OWASP minimum. For new systems on modern servers, cost 12 gives a meaningful security margin without creating unacceptable login latency. Cost 14 is reasonable for batch or background jobs where latency does not affect user experience. Anything above 16 risks denial-of-service at scale and should only be used with a carefully measured threat model.
bcrypt vs MD5 / SHA-256 vs Argon2id vs scrypt vs PBKDF2
| bcrypt | PBKDF2-HMAC-SHA-256 | scrypt | Argon2id | |
|---|---|---|---|---|
| Year | 1999 | 2000 | 2009 | 2015 |
| Deliberate cost | CPU (iterations) | CPU (iterations) | CPU + memory | CPU + memory + threads |
| GPU/ASIC resistance | Moderate | Low | High | Very high |
| Library availability | Every major language | Every major language | Most languages | Growing (most languages) |
| OWASP recommendation | Accepted (legacy) | Accepted | Accepted | Recommended |
| 72-byte input limit | Yes | No | No | No |
The table tells the migration story clearly: Argon2id is the strongest choice, followed by scrypt for memory-constrained environments, PBKDF2 where compliance mandates it, and bcrypt as the battle-tested survivor. All four are vastly better than hashing a password with plain SHA-256, which offers zero resistance to high-throughput GPU attacks.
Why bcrypt is still a solid choice
Despite being the oldest option in the table, bcrypt has a crucial operational advantage: every major platform ships a well-tested bcrypt library. PHP, Python, Ruby, Node.js, Go, Rust, Java, and C# all have native or near-native bcrypt bindings, which means moving to bcrypt rarely requires architectural changes.
The 72-byte limit is well-documented, and pre-hashing with SHA-256 lets bcrypt process longer inputs. The cost factor is tunable in a straightforward way. The output encoding is self-describing. These operational properties — more than raw cryptographic superiority — are why bcrypt remains the default in Laravel, Django, Rails, Express, and countless production authentication stacks in 2026.
The recommendation is not to stay on bcrypt forever; it is that replacing bcrypt with Argon2id should be a conscious migration, not a panic. For systems where bcrypt is already deployed and audited, the security benefit of migrating is small compared to the risk of introducing a subtle implementation bug in the new path.
How the AnyHash bcrypt tool works
- Pure JavaScript, no Web Crypto. The Web Crypto API does not expose bcrypt, so
the hash is computed using
bcryptjs, a widely-used pure-JS Blowfish implementation specifically designed for password hashing. - Auto-generated salt. Every time you type a password, the tool generates a fresh
cryptographically random 16-byte salt via
crypto.getRandomValuesand embeds it in the output string. The same password produces a different hash on every run — this is by design. - Debounced computation. Keystrokes schedule a new hash after 240 ms, so the UI stays responsive. At cost 10, the computation completes in roughly 60–100 ms on a modern laptop; at cost 16, the status bar shows a progress indicator while the computation runs in the background.
- 72-byte warning. The tool counts the UTF-8 byte length of your input before hashing and displays a clear warning when the limit is exceeded, so there is no silent truncation.
- Hash / Verify tabs. The verify tab accepts a password and a bcrypt hash string, re-derives the key from the hash's embedded parameters, and reports whether the two match. No data is transmitted to a server at any point.
Verification, deep dive
The verify tab in this tool does exactly what a production auth system does: it reads the version, cost, and salt from the hash string, runs the EksBlowfish key schedule with those parameters, derives the candidate key, and compares it against the stored key byte by byte. Because the cost factor is embedded in the hash, a system can rehash passwords at a higher cost over time without resetting any stored credentials — every verification automatically runs at the stored cost.
If you want to confirm this, copy any hash you generate and paste it into a different bcrypt
library — Python's bcrypt.checkpw(), for instance — with the same password. The
result will always be True. The hash encoding is portable across every standards-
compliant bcrypt implementation.
Migrating from bcrypt to Argon2id
If you are planning a migration, the recommended approach is a compare-and-upgrade pattern: when a user logs in with the correct password, rehash the password under Argon2id and store the new hash alongside the old one. This way, each user migrates on their next login without a forced password reset. New accounts and password changes should be hashed with Argon2id from the start.
The AnyHash Argon2id tool lets you experiment with parameters — memory, iterations, and parallelism — against the same password, so you can benchmark different settings before committing to a configuration in production.
$2a, $2b, $2y — the version prefixes
A bcrypt hash string embeds its own version marker. The first element after the leading
$ identifies which implementation produced the hash — and, surprisingly, this matters
for interop:
-
$2a$was the original marker, added by the OpenBSD reference implementation and adopted by almost every library. A historical 2003 bug in OpenBSD's original implementation mishandled 8-bit (non-ASCII) bytes, but modern$2a$implementations are correct — the marker stuck for compatibility. -
$2b$was introduced in 2011 by OpenBSD to fix that historic bug formally. It signals “correct handling of 8-bit passwords” and is the marker most contemporary libraries generate today (including the Pythonbcryptpackage and thebcryptjslibrary this page uses). A$2b$hash validates with any correct bcrypt implementation regardless of prefix. -
$2y$is a PHP-specific alias that signals the PHP fix for the same bug. It is fully interoperable with$2a$/$2b$in practice; PHP's ownpassword_hash()generates$2y$by default.
The money takeaway: treat the version prefix as metadata, not as something to compare exactly. A
verify function that rejects hashes merely because the prefix differs (e.g. accepting only
$2a$, or only $2b$) breaks 15 years of deployed passwords for no
security gain. The password that validates against a $2b$ modern hash will validate
against a standards-compliant verifier that accepts any of the three markers.
The economics of the cost factor
The cost factor c means bcrypt runs its key schedule 2c times. That exponential is the lever that keeps old hashes safe as hardware improves: each increase of 1 doubles the work, and each doubling compounds over the years a password stays in the database.
Attack economics make the trade-off concrete. Off-the-shelf GPU rigs today crack bcrypt at roughly tens-of-thousands to hundreds-of-thousands of guesses per second at cost 10 (per-hash work scales with the cost you choose). Cost 11 halves that throughput; cost 12 quarters it, and so on. Compare this with a fast digest like SHA-256, which GPU rigs test at trillions of hashes per second — the margin between “transparently weak” and “practically resistant” is exactly the memory-free, deliberately slow design that bcrypt imposes.
The right cost factor is nevertheless not “the highest available” — it is the highest your real login path tolerates in aggregate. A million active users don't share the cost of one hash; every login, password reset, and session re-auth pays it. A pragmatic selection procedure:
- Benchmark bcrypt on your production hardware across a range of costs (for example, 10 through 15) using the exact library you will ship.
- Tabulate the per-hash latency for your peak concurrency (do not measure a single hash at idle — measure a synthetic burst).
- Pick the highest cost where the 99th-percentile login still completes within your SLO (typically 250 ms–1 s to hash).
- Re-run the benchmark annually or whenever you move to materially faster hardware, and raise the cost at the next natural migration.
Because the cost is stored inside each hash, raising it requires no database migration — as the verification deep-dive demonstrates, every future check simply runs at the stored cost. The hidden cost of staying on an old, low cost factor is far larger than the visible cost of raising it.
Reading a bcrypt hash string
A bcrypt hash is a self-describing string. Take the following hash, generated with the same library
this tool uses, at cost 12 for the password correct horse battery staple:
$2b$12$25RkZGNmZqarn0GtfYjgN.2Lsl6aGloginW.W9dwFAq9PTZK0cXTq $2b$— version marker (see the previous section)12— cost factor, meaning the key schedule runs 212 = 4,096 times25RkZGNmZqarn0GtfYjgN.— the 22-character Base64 encoding of the 16-byte salt2Lsl6aGloginW.W9dwFAq9PTZK0cXTq— the truncated 31-character Base64 key (the Blowfish output, chopped to fit the 60-character total)
Verify it yourself: paste the hash above into the verify tab with the password
correct horse battery staple, and it should report a match. Because the hash embeds
its own version, cost, and salt, any compliant bcrypt verifier — this page, Python's
bcrypt.checkpw(), Ruby's BCrypt::Password#== — will accept it. The
digest is only 184 bits: bcrypt was designed for password hashing, not for general-purpose
integrity checks, so it keeps its output deliberately compact and slow to compute.
Where bcrypt wins in practice today
It is fair to ask why, in 2026, a new system would still pick a 1999 algorithm. The honest answer: bcrypt's weaknesses are architectural (no memory-hardness), and its strengths are operational (ubiquity, stability, and decades of production scrutiny). Consider your real constraints before ruling it out.
- Library availability. bcrypt ships or is one install away in every mainstream
language and framework — PHP's
password_hash()defaults to it, Ruby's Rails hashas_secure_passwordbuilt on it, and Java/Go/Rust/Python all have mature bindings. Argon2 support is improving but is not yet uniform across every older stack you may be forced to interoperate with. - Legacy data. Existing bcrypt hash columns validate fine indefinitely; the cost factor is embedded, so you can raise it without migrating. A system that already stores bcrypt hashes is better served by raising the cost than by a risky in-place algorithm swap.
- Known behavior. bcrypt has been analyzed for over two decades, and its properties are well documented. For a team without a dedicated cryptographer, choosing the algorithm with the longest successful deployment history is often the more defensible decision than adopting the newest one on a tight schedule.
The deciding factor is usually threat model and roadmap: if you are writing greenfield code on a modern stack and can afford the memory, Argon2id is the stronger choice; if you are maintaining a widely deployed system on a constrained stack, a properly costed bcrypt is still a solid, non-emergency posture.