DevKit
← Back to Blog

bcrypt vs SHA-256 for Passwords: Why Fast Hashing Is Wrong

8 min read

A frequent and dangerous mistake is storing passwords with SHA-256. It is a hash, it produces a fixed-length output, and it feels secure. For passwords, though, it is the wrong tool, and the reason comes down to a property that makes SHA-256 excellent everywhere else: speed.

Why fast hashing fails for passwords

SHA-256 is designed to be fast. A modern GPU can compute billions of SHA-256 hashes per second. That is wonderful for verifying file integrity, but it is a catastrophe for passwords. If an attacker steals your database, they do not try to reverse the hash; they guess common passwords, hash each guess, and compare. The faster the hash, the more guesses per second, and billions of guesses per second means weak passwords fall almost instantly.

Plain SHA-256 has a second problem: identical passwords produce identical hashes. An attacker can precompute a huge table of common passwords and their hashes once, then match it against any database. These are called rainbow tables.

What a password hash actually needs

A password hashing function should be deliberately slow and should produce a different output for the same input every time. Two mechanisms deliver this: a salt and a cost factor.

A salt is a random value mixed into each password before hashing. Because every password gets a unique salt, two users with the same password end up with different stored hashes, and precomputed rainbow tables become useless.

A cost factor makes the function run a tunable number of iterations. As hardware gets faster, you raise the cost to keep the function slow enough that mass guessing stays impractical.

How bcrypt handles this

bcrypt builds both features in. It generates a salt for you, applies a configurable cost factor, and packages everything into a single self-describing string:

$2b$12$R9h/cIPz0gi.URNNX3kh2OPST9/PgBkqquzi.Ss7KIUgO2t0jWMUW
 |  |  |                     |
 |  |  |                     +-- 31-char salt + hash
 |  |  +-- cost factor (12)
 |  +-- (minor version)
 +-- algorithm identifier

Everything needed to verify a password later lives inside that one string: the algorithm, the cost, and the salt. You store the whole thing in a single column. When a user logs in, bcrypt reads the cost and salt back out of the stored hash, applies them to the submitted password, and compares. There is no separate salt column to manage.

The cost factor is a power of two for the work involved. Raising it from 10 to 11 roughly doubles the time to compute each hash. A value in the range of 10 to 12 is a reasonable starting point on current hardware; measure on your own servers and pick the highest value your login latency can tolerate.

The 72-byte limit

bcrypt only considers the first 72 bytes of the input. Anything beyond that is silently ignored, so a very long passphrase can have its tail truncated without warning. This rarely matters for ordinary passwords, but it causes a subtle bug in one specific pattern: pre-hashing. Some developers run a password through SHA-256 first and feed the hex result into bcrypt, which can run into both the length limit and null-byte issues depending on encoding. If you need to support arbitrarily long inputs, prefer a function without this constraint rather than stacking hashes.

argon2 and scrypt

bcrypt is a solid, battle-tested choice, but it is not the only one. Both scrypt and argon2 add memory hardness, meaning they require a large amount of memory as well as time. This matters because attackers increasingly use GPUs and custom hardware that can parallelize cheap-memory work; forcing high memory usage levels the field.

argon2, specifically the argon2id variant, is the current recommendation for new systems from most security guidance. It lets you tune time, memory, and parallelism independently. scrypt is a good older option with similar memory-hard properties. If your platform offers argon2id, it is the strongest default; if not, bcrypt remains perfectly acceptable.

Practical rules

To see what a bcrypt hash looks like for a given input and cost, try the bcrypt Generator. And if you want to understand the contrast firsthand, run the same text through the Hash Generator and notice how a SHA-256 digest appears instantly, which is exactly the speed you do not want guarding a password.