Bcrypt Explained: Salts, Cost Factors and Storing Passwords Safely

Why fast hashes are wrong for passwords, what each part of a $2b$ string means, how to pick a cost factor and where bcrypt fits next to Argon2 and scrypt.

Storing passwords is one of the few areas of software where there is a clear right answer, and getting it wrong has a long public history. Breaches of LinkedIn in 2012 and of many other services exposed millions of passwords stored with fast, unsalted hashes, and most of them were cracked within days.

Bcrypt has been one of the right answers for over two decades. This guide explains why fast hashes fail, how bcrypt works, how to read a bcrypt string, how to choose a cost factor, and where bcrypt sits next to Argon2 and scrypt. Follow along in the Bcrypt Hash Generator.

Why fast hashes fail for passwords

General-purpose hashes like SHA-256 are designed to be fast. That is great for checksums and terrible for passwords. When an attacker steals a database of password hashes, they guess offline: hash a candidate password, compare, repeat. Speed is exactly what they want. Modern GPUs compute billions of SHA-256 hashes per second.

Two defences are needed:

  1. A unique salt per password, so identical passwords produce different hashes and precomputed lookup tables (rainbow tables) are useless.
  2. Deliberate slowness, so each guess costs real time and compute.

Bcrypt provides both.

Origins

Bcrypt was presented by Niels Provos and David Mazières at USENIX in 1999 in the paper "A Future-Adaptable Password Scheme". It was built for OpenBSD and is based on Bruce Schneier's Blowfish cipher. Bcrypt uses a deliberately expensive variant of Blowfish's key setup, called EksBlowfish, which is repeated 2^cost times.

The phrase "future-adaptable" is the key idea. As hardware gets faster, you increase the cost factor and hashes become proportionally more expensive to compute, without changing the algorithm.

Reading a bcrypt hash

A bcrypt hash is a 60-character string that contains everything needed to verify it:

$2b$12$R9h/cIPz0gi.URNNX3kh2OPST9/PgBkqquzi.Ss7KIUgO2t0jWMUW
\__/\_/\____________________/\_____________________________/
 |   |          |                          |
 |   |      salt (22 chars)          hash (31 chars)
 |   cost factor (2^12 rounds)
 algorithm version
  • $2b$: the version. You will also see $2a$ and $2y$. 2b was introduced in 2014 to fix a bug in OpenBSD's handling of very long passwords; 2y is PHP's marker for its corrected implementation. Modern libraries verify all of them.
  • 12: the cost factor. The work is 2^12 iterations.
  • 22 characters of salt: 128 bits of random salt, in bcrypt's own Base64 alphabet.
  • 31 characters of hash.

Because the salt and cost are stored in the string, verification needs only the password and the stored hash. That is also why the generator gives a different hash every time you hash the same password: a new random salt each time.

Choosing a cost factor

Each increment of the cost doubles the time. If cost 10 takes 60 ms on your server, cost 11 takes about 120 ms and cost 12 about 240 ms.

OWASP's Password Storage Cheat Sheet recommends a cost of at least 10. The practical way to choose is to benchmark on your production hardware and pick the highest cost that keeps a login comfortably fast, commonly somewhere between 50 and 250 ms. Remember that every login and every registration pays this cost, and an attacker can use it for denial of service by hammering your login endpoint, so rate limiting matters too.

The generator lets you try costs from 4 to 16. Try 10, then 14, and feel the difference: that slowdown is the protection.

Upgrading the cost later

Because the cost is stored in each hash, you can raise it over time without a migration. On each successful login, check the cost in the stored hash. If it is below your current target, re-hash the password, which you have in plain text at that moment, and save the new hash.

Bcrypt's limitations

  • 72-byte limit. Bcrypt only uses the first 72 bytes of the password. For normal passwords this rarely matters, but multi-byte UTF-8 characters count as several bytes, and some systems pre-concatenate a pepper or username. Some libraries silently truncate; others throw an error. If you pre-hash long inputs with SHA-256 to get around the limit, Base64-encode the digest first, since raw digest bytes can contain zero bytes that some implementations treat as the end of the string.
  • Low memory use. Bcrypt needs only about 4 KB of memory, so it can be attacked efficiently with GPUs and custom hardware compared with memory-hard functions.

Bcrypt, scrypt and Argon2

AlgorithmYearMemory-hardNotes
PBKDF22000NoFIPS-approved; needs very high iteration counts
bcrypt1999No (about 4 KB)Mature, widely supported
scrypt2009YesDesigned by Colin Percival
Argon2id2015YesWinner of the Password Hashing Competition

Argon2 won the Password Hashing Competition in 2015 and is specified in RFC 9106. Its Argon2id variant is OWASP's first recommendation for new systems, with scrypt next, and bcrypt as an acceptable choice for systems that already use it or where Argon2 is not available. PBKDF2 is recommended where FIPS-140 compliance is required.

In practice, a well-configured bcrypt deployment is vastly better than the fast-hash mistakes that cause real breaches. If you are starting fresh and your platform has a good Argon2id library, use it. If you have bcrypt today with a reasonable cost factor, you are in good shape.

Implementation tips

  1. Use a maintained library: bcrypt or bcryptjs in Node.js, bcrypt in Python, password_hash() in PHP, golang.org/x/crypto/bcrypt in Go, Spring Security's BCryptPasswordEncoder in Java.
  2. Use the library's compare function rather than comparing strings yourself; it is designed to resist timing attacks.
  3. Never log passwords, even on failed logins.
  4. Rate-limit login attempts and support multi-factor authentication.
  5. Consider a pepper: a secret stored outside the database and combined with the password, so a database-only leak is not enough to start cracking.

About the generator

The RedBison Bcrypt Hash Generator uses the bcryptjs library, a pure JavaScript implementation compatible with standard bcrypt hashes. It is useful for creating test user hashes for seed data, checking whether a stored hash matches an expected password, and exploring how cost factors behave. Everything runs in your browser. As a habit, use test passwords rather than real credentials in any online tool.

Related tools

Launch Bcrypt Hash Generator