Paste any BCrypt hash to visually decode its every segment β version, cost factor, salt and digest β with color-coded annotations, timing analysis and deep-dive educational content.
A BCrypt hash is always exactly
60 characters
: 4-char prefix (
$2b$
), 2-digit cost,
$
separator, 22-char Base64 salt, 31-char Base64 digest. No separators between salt and digest β the split is positional at character 29 from the cost
$
.
BCrypt silently truncates passwords to 72 bytes. "password123456β¦" (73+ chars) hashes identically to the first 72 chars. Mitigation: pre-hash with SHA-256/512 before BCrypt, or use Argon2/scrypt. Some libraries (like bcryptjs) warn about this.
BCrypt uses a custom Base64 alphabet:
./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789
β not the standard RFC 4648 alphabet. The output is
not
decodable with standard
atob()
or standard Base64 libraries without remapping.
SHA-256 runs at billions of hashes per second on modern GPUs (A100: ~100 GH/s). A BCrypt cost 12 hash takes ~250ms β roughly 25 billion times slower. An attacker cracking SHA-256 in 1 second would need 25 billion seconds (>790 years) with BCrypt cost 12.
| Algorithm | Year | Memory hardness | Max password length | Current recommendation |
|---|---|---|---|---|
| bcrypt | 1999 | No | 72 bytes (silently truncates) | Still solid for most web apps β wide library support, 25 years of audits, default in many frameworks |
| scrypt | 2009 | Yes | Unlimited | Good β memory-hard defeats GPU attacks better; used by some Linux distros for /etc/shadow |
| Argon2i | 2015 (PHC winner) | Yes | Unlimited | Best for password hashing β side-channel-resistant variant; OWASP #1 recommendation since 2019 |
| Argon2id | 2015 (PHC winner) | Yes | Unlimited | Best overall β hybrid of Argon2i and Argon2d; OWASP primary recommendation for new systems |
| PBKDF2-SHA256 | 2000 | No | Unlimited | Acceptable β FIPS-approved; required in US government contexts; needs 600,000+ iterations in 2024 |
| MD5 / SHA-256 (bare) | β | No | Unlimited | Never β nanosecond hashes, billions per second on GPU, no protection at all |
Bcrypt hashes only the first 72 bytes of the input. A password of 73 bytes and a password of 72 bytes that share the same first 72 bytes hash identically. This means someone whose password is 100 characters long effectively has a 72-character password β without being told. Mitigate by pre-hashing: bcrypt(base64(sha256(password))) before passing to bcrypt. This converts an arbitrarily long password into a fixed-length, full-entropy input.
Bcrypt was designed in 1999 for hardware that was 10,000Γ slower. Cost factor 10 meant ~100ms per hash then. On a modern server, cost 10 takes ~10ms. OWASP currently recommends cost 12 as a minimum, targeting 250ms+ per hash on your production hardware. Run a benchmark on your own server and pick the cost factor that keeps login at 250β500ms. Bump it every few years as hardware improves.
Unlike SHA-256 where you store the salt separately, bcrypt embeds a random 128-bit salt directly in the 60-character hash string: $2b$12$[22-char salt][31-char digest]. This means you only store one string per user, not a hash + salt pair. The verification function parses the embedded salt automatically. Never strip or separate the parts β the whole string is the stored credential.
$2a$ had a bug in some implementations (incorrect handling of 8-bit chars). $2b$ (2011) is the corrected standard. $2y$ is PHP's equivalent of $2b$. For new code, always use a library that generates $2b$. Libraries that still generate $2a$ are outdated. All modern bcrypt implementations can verify hashes with any prefix.
Some developers pre-hash with SHA-256 and then also apply bcrypt β which is fine if done correctly. But re-running bcrypt on an already-bcrypt-hashed value (e.g., when updating cost factor) produces a double-bcrypt hash that's extremely difficult to manage. To update cost factors for existing users, re-hash only on their next successful login when you have the original password.
A "pepper" is a site-wide secret mixed into password hashes (separate from the per-user salt). If an attacker steals your database, the pepper stored in application config means they can't crack hashes without also stealing the config. Implement it as a pre-hash step: HMAC-SHA256(pepper, password) before bcrypt. Store the pepper version number alongside the hash so you can rotate it without invalidating all hashes.
verify() function handles this automatically.