Tools
Guides
On this page

August 9, 2026

bcrypt vs SHA-256: choosing the right hash for password storage

Every few months a breach database surfaces containing millions of password hashes, and the same question gets asked in post-mortems and code reviews alike: why were they stored with MD5? Why SHA-256? That was supposed to be fine — it’s a cryptographic hash. The confusion is understandable, because it sounds like a contradiction: SHA-256 is a secure hash function, and storing passwords with a secure hash function is insecure. Both halves of that sentence are true. The problem is that password storage is not an integrity problem, and the property that makes SHA-256 excellent at integrity — speed — is precisely the property that makes it catastrophic here.

This guide explains the economics of offline password cracking, walks through what a bcrypt hash actually contains, shows how the cost factor changes real attack economics, and gives you a decision rule you can apply today. You can reproduce every example interactively: the bcrypt hash & verify tool runs entirely in your browser, and the hash calculator shows the fast-hash side of every comparison.

The threat model: the attacker already has your database#

The first step to understanding password hashing is accepting the premise of the attack. When a password database is stolen, the attacker holds a copy of every hash and can guess offline, at full speed, with no rate limiting, no lockouts, no alerts — nothing between them and the answer except the work your hash function demands per guess.

Under that premise, hashing is an economic defense, not a cryptographic one. You are not making recovery impossible; you are setting the price per guess high enough that trying the billions of plausible passwords becomes unaffordable. Every design choice follows from that framing.

Now look at the two contenders through that lens.

SHA-256 is a general-purpose hash designed for throughput. On the commodity machine this guide was written on, a single Node.js process computed 100,000 SHA-256 digests in 191 milliseconds — roughly half a million per second on one CPU core, in an interpreted runtime, doing no work in parallel. On a GPU rig or a cloud instance fleet, optimized implementations reach billions of guesses per second. MD5 is even faster, and that is the entire reason breached MD5 databases historically fell within days.

bcrypt is a hash function engineered for the opposite of throughput. It is deliberately expensive per single invocation, based on the Blowfish cipher’s key schedule, and it carries a tunable cost factor so the expense can be raised as hardware improves. On the same machine: cost 10 took about 60 milliseconds per hash, cost 12 about 245 milliseconds, cost 14 about 956 milliseconds. That is six orders of magnitude slower than SHA-256 — which for a user typing a password into a login form is imperceptible, and for an attacker working through a stolen database is ruinous.

The comparison that matters, side by side:

Algorithm   One hash (this machine)   Guesses/sec, one core (approx)
SHA-256     ~0.002 ms                 ~500,000 (interpreted JS)
bcrypt 10   ~60 ms                    ~17
bcrypt 12   ~245 ms                   ~4
bcrypt 14   ~956 ms                   ~1

A useful analogy: for file integrity, you want a scale that weighs a truck once and moves on. For password storage, you want a door that takes a quarter second to open for the person with the key — and takes a quarter second every single time for someone trying every key in the building.

Why “SHA-256 plus a salt” is still not enough#

A common rebuttal: okay, plain SHA-256 is bad, but I salt it. Salting does solve two real problems — identical passwords no longer produce identical hashes, and precomputed tables (rainbow tables) become useless, because the attacker must re-run the cracking effort separately for every unique salt. If you are currently storing unsalted SHA-256, adding a per-user salt is a genuine improvement.

But a salt changes what the attacker must do, not how much each guess costs. The attacker with your database sees the salt in the clear (it has to be readable, or you could not verify logins), appends it to each candidate password, and hashes at full GPU speed. Salting defeats table reuse; it does nothing about brute-force throughput. The speed of the underlying function remains the dominant term in the attack cost — and SHA-256’s speed is the one thing you cannot tune.

A second, subtler failure of fast hashes is the arms race they enable. When GPUs get faster, a SHA-256-based scheme cannot respond; the hashes you already stored simply become weaker every year. What password storage needs is a scheme where the defender can dial the work factor upward over time. That is exactly what bcrypt’s cost factor provides.

Anatomy of a bcrypt hash#

Here is a real hash of the passphrase correct horse battery staple at cost 12, produced by this site’s own tool:

$2b$12$LlQgG7M9xgdiW1SIvj/aT.1leQr0RcWT/x7GSbLgwFMt.916Uxy8y

Read it field by field:

  • $2b$ — the variant tag. $2a$ is the original OpenBSD scheme, $2b$ fixed an obscure wraparound bug in some implementations, $2y$/$2x$ appear in PHP ecosystems. All encode the same core algorithm; verifiers accept the lot.
  • 12 — the cost factor. The key schedule is iterated 2^cost times.
  • the next 22 characters — the salt, randomly generated per hash and encoded in bcrypt’s base64 alphabet (./A-Za-z0-9).
  • the final 31 characters — the derived hash itself.

Two practical consequences fall out of this layout.

The salt is built in and portable. You do not manage a separate salt column, and verification needs nothing beyond the password and this one string — the cost and salt ride along inside it. Paste the string above into the tool’s Verify mode, type correct horse battery staple, and the verdict comes back match; the tool read the cost from the hash itself. Hash the same passphrase again and you get a completely different string (fresh salt), which also verifies — that is salting working as intended, not instability.

The cost is exponential, which is the whole trick. Each increment of 1 doubles the work. Cost 4 takes about a millisecond; 10 about 60 ms; 12 about 245 ms; 14 about 960 ms — every +2 is roughly 4x. Your users pay it once per login attempt; an attacker pays it for every candidate password in every account. A 100-million-entry dictionary against a 10,000-user database at cost 12 is not a weekend job — it is a small datacenter-month.

Two limitations you should know about. bcrypt truncates input at 72 bytes — longer passphrases are effectively cut off, so prepend a fast hash if you must support long inputs (and only with a well-reviewed recipe). And bcrypt’s memory use is tiny, so it is fully parallelizable on GPUs — modern designs like Argon2 add memory hardness precisely to raise that bar further.

Choosing and living with a cost factor#

The cost factor is not a constant to set once. It is a dial you re-tune as hardware improves, and the right value is a function of your users and your threat model:

  • Measure on your production hardware, not on a laptop or in a guide. The target most practitioners converge on: a single verification somewhere in the 100–500 ms range. On typical 2026 server CPUs that lands around cost 12–14.
  • Weigh it against what you are protecting. A forum can afford 100 ms; a high-value application should aim for the upper end and accept a login that feels deliberate. The cost must also be honest about login bursts — a 500 ms verification serialized across concurrent logins multiplies CPU load accordingly.
  • Re-tune upward on a schedule. Because cost is exponential, hardware gains erode it slowly, but relentlessly. Review the number yearly; raising it requires no migration, since each hash carries its own cost and new logins immediately store the higher cost.
  • Never go below 10 for anything internet-facing. Costs 4–9 exist for tests and benchmarks, not production.

The bcrypt tool makes the dial tangible: its slider runs 4–14, it reports the real elapsed time of every operation on your machine, and at cost 12 and above the work moves to a background worker thread so the page stays responsive. Spend two minutes hashing one password at 4, 10, 12, and 14, and you will never again read “cost 12” as an abstract number.

For completeness, the modern landscape: Argon2id (the Argon2 family’s recommended variant) is the current state of the art and what new systems should generally choose when the option exists — it adds a memory parameter that makes GPU and ASIC attacks far more expensive per guess. scrypt is an earlier memory-hard design in the same direction. bcrypt remains a fully defensible, battle-tested choice with twenty-plus years of production scars and universal library support; the sharp line is between any of these slow hashes and no slow hash at all.

FAQ#

Is SHA-256 broken, then?#

No — SHA-256 remains unbroken for its actual jobs: file integrity, HMAC signing, certificates, content addressing. The mistake is category confusion: SHA-256 is a fast integrity hash, and password storage needs a slow one. Use each for its own job; the hash calculator is for the former, the bcrypt tool for the latter.

What cost should I actually deploy with?#

Start at 12, verify against your real login latency and CPU budget, and move up from there if you can. The test is empirical: measure single-hash wall time on production-like hardware and aim for the 100–500 ms band. Anything below cost 10 on an internet-facing system is under-defended by 2026 standards.

Why does hashing the same password twice give different strings?#

Because bcrypt generates a fresh random salt on every hash and embeds it in the output. The two strings look unrelated yet both verify against the same password. This is the feature working: an attacker cannot tell that two users chose the same password, and cannot reuse any precomputed work across databases.

How do I migrate an existing SHA-256 (or MD5) database without forcing everyone to reset?#

Use lazy rehashing at login: keep the old hash; when a user logs in successfully, verify against the old scheme, then immediately hash the just-supplied password with bcrypt and replace the stored value. Users who never log in again keep the weak hash — prompt them by email, or eventually force a reset — but every active account migrates invisibly. Never hash the old hash as a shortcut without documenting it; the cleanest schemes re-derive from the plaintext at login time.

Does bcrypt’s 72-byte limit matter for my users?#

Rarely, in practice: 72 bytes covers virtually all typed passwords, and attackers cannot exploit the truncation to help themselves — but a 120-character passphrase quietly loses its extra length. If you support long passphrases, either cap input length at the form level, or adopt Argon2id, which has no such limit.

Should I add a pepper on top?#

A pepper — a secret value stored outside the database, mixed into every hash — adds a genuine speed bump for the database-only attacker, and bcrypt can be peppered by hashing the password with an HMAC keyed by the pepper before bcrypt. It is a reasonable hardening layer for mature teams, but it introduces key-management obligations (rotation, HSMs, availability) that most systems underestimate. Get slow hashing and salting right first; treat a pepper as an advanced, optional extra.

Where to go next#

  • Hash and verify real strings at different cost levels with the bcrypt hash & verify tool — slider from 4 to 14, automatic cost detection on verify, everything computed locally.
  • See exactly how fast the fast side is: feed correct horse battery staple to the hash calculator — its SHA-256 digest is c4bbcb1fbec99d65bf59d85c8cb62ee2db963f0fe106f483d9afa73bd4e39a8a.
  • Generate high-entropy passphrases that justify the storage effort with the password generator, and inspect salts with the salt generator.

← All guides