Every week brings another news story about a company whose user passwords were leaked in a data breach. In the worst cases, those passwords were stored in plaintext: a mistake so serious it borders on negligence. In slightly better cases, they were hashed with MD5 or SHA-1: algorithms so fast that an attacker with a modern GPU can test hundreds of millions of guesses per second. Bcrypt was designed specifically to prevent this.
Why fast hash functions are wrong for passwords
MD5, SHA-1, SHA-256, and their relatives are general-purpose cryptographic hash functions. They were designed to be fast: processing large amounts of data quickly for integrity verification, digital signatures, and similar tasks. That speed is a fatal flaw when used for password storage. A modern consumer GPU can compute around 10 billion MD5 hashes per second. An attacker with a list of leaked hashes can test every word in the English language, every common password variation, and every combination of letters and numbers up to a certain length in a matter of hours.
The solution is to use a hash function that is intentionally slow: one where each password check takes a measurable amount of time (tens or hundreds of milliseconds) rather than microseconds. This makes the difference between an attacker testing 10 billion guesses per second and testing a few thousand. Bcrypt is the most widely adopted algorithm designed with this property.
How bcrypt works
Bcrypt was designed by Niels Provos and David Mazières in 1999, based on the Blowfish cipher. Its defining feature is a cost factor (also called salt rounds or work factor) that controls how slow the hashing process is. The cost factor is expressed as a power of two: a cost of 10 means the algorithm performs 2^10 (1,024) rounds of its key schedule. A cost of 11 doubles the computation time. A cost of 12 doubles it again.
Each bcrypt hash also contains a randomly generated salt: 128 bits of random data generated fresh for each password. The salt is embedded in the hash output alongside the cost factor, so you never need to store it separately. Because the salt is unique per password, two users with the same password will produce completely different hashes, making precomputed rainbow table attacks useless.
Reading a bcrypt hash
A bcrypt hash looks like this: $2b$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWy. Each part has a meaning. The $2b$ identifies the bcrypt algorithm version. The $10$ is the cost factor. The next 22 characters are the Base64-encoded salt. The remaining 31 characters are the actual hash. When you verify a password, the library extracts the cost factor and salt from this string and uses them to hash the candidate password in exactly the same way: if the result matches the stored hash, the password is correct.
Choosing the right cost factor
- •Cost 10: the traditional default, appropriate for most applications. Takes around 100ms on a modern server.
- •Cost 12: the current OWASP recommendation for new applications. Takes around 400ms on a modern server.
- •Cost 14: appropriate for high-security applications where login latency is acceptable. Takes around 1.5 seconds.
- •Do not go below cost 10 in production. The marginal performance gain is not worth the security regression.
- •As hardware improves over time, increase the cost factor for new registrations and re-hash existing passwords on login.
The 72-byte input limit
Bcrypt silently truncates input at 72 bytes. A password of 73 characters or more will produce the same hash as the first 72 characters. For most users this is not a practical concern, but it is worth knowing. If you want to support arbitrarily long passwords securely, a common technique is to SHA-256 the password first (producing a 32-byte output well within the 72-byte limit) and then bcrypt the result. Some libraries handle this automatically.
Bcrypt in practice: Node.js example
In Node.js, the bcrypt and bcryptjs libraries are both widely used. With bcryptjs (pure JavaScript, no native dependencies): const hash = await bcrypt.hash(password, 12) to hash, and const match = await bcrypt.compare(candidate, storedHash) to verify. The cost factor is passed to hash() and embedded in the output: you never need to pass it again when verifying.
Alternatives to bcrypt
Argon2 is the algorithm that won the Password Hashing Competition in 2015 and is now OWASP's first recommendation for new applications. It improves on bcrypt by also being memory-hard, meaning attackers cannot use dedicated hardware (ASICs) as effectively. PBKDF2 is another widely supported alternative with similar properties to bcrypt. All three are vastly better than any general-purpose hash function for password storage.
AlteredIdea's Bcrypt Hash Generator lets you hash passwords and verify hashes directly in your browser at any cost factor. Nothing is transmitted: the bcryptjs library runs entirely on your device.