// IT TOOLS & CALCULATORS
| 100+ TOOLS
๐Ÿ“š PASSWORD HASHING: WHY BCRYPT, NOT JUST SHA-256
Security guide ยท 17 Sep 2026 ยท 7 min read

SHA-256 is a perfectly good hash function. It's also, for the specific job of storing passwords, close to the wrong tool for the job โ€” not because it's broken or insecure in any general sense, but because it's fast, and fast is precisely the property you don't want when the input is something an attacker can and will try to guess offline, billions of times a second, on hardware built for exactly that. The confusion is understandable: both SHA-256 and bcrypt are called "hash functions," both take an input and produce a fixed-length output, and both are one-way in the sense that you can't reverse the output back into the input directly. But they were built to solve different problems, and password storage needs the one that's deliberately, expensively slow.

What a fast hash is actually for

SHA-256, and general-purpose cryptographic hashes like it, exist to verify integrity โ€” proving a file wasn't tampered with, checking that a downloaded package matches its published checksum, building the backbone of a blockchain, deriving a fixed-length identifier from arbitrary data. In every one of those use cases, speed is the entire point: you want to be able to hash gigabytes of data quickly, verify millions of blocks per second, or check a checksum without a noticeable delay. A modern GPU can compute billions of SHA-256 hashes per second, and that's a feature for every use case it was actually designed for.

It becomes a liability the instant the input is a password and the threat model is an attacker who has already obtained the hash โ€” from a leaked database, a misconfigured backup, an insider, whatever the source โ€” and now wants to recover the original password offline, with no rate limiting, no lockout policy, and no network round-trip slowing them down. At that point, hash speed isn't a performance feature; it's a multiplier on how many guesses the attacker can try per second. A password hashed with plain SHA-256 can be attacked at a rate limited only by the attacker's hardware, and consumer GPUs now do billions of SHA-256 computations a second โ€” enough to exhaust the entire keyspace of a short or dictionary-based password in a timeframe measured in hours, not years.

What bcrypt does differently

Bcrypt (and its more modern relatives, scrypt and Argon2) is built around a completely different design goal: make each individual hash computation deliberately expensive, on purpose, by design, with the cost tunable and expected to increase over time as hardware gets faster. Where SHA-256 is optimised to be as fast as possible, bcrypt is optimised to be as slow as tolerable โ€” slow enough that computing one hash takes a noticeable fraction of a second even on capable hardware, but not so slow that a legitimate login becomes annoying. That asymmetry is the whole design: a real user hashing their own password once per login barely notices a 100-300ms delay; an attacker trying to hash billions of guesses against a stolen database sees that same delay multiplied across every single guess, which is the difference between a crackable password list and one that stays impractical to crack for years.

Bcrypt achieves this with a work factor (sometimes called a cost factor) โ€” a number, typically stored alongside the hash itself, that determines how many rounds of internal computation the algorithm runs. Raising the work factor by 1 roughly doubles the computation time, which means the same algorithm can be made proportionally slower as attacker hardware gets faster, without ever changing the underlying function. This is the property that plain fast hashes fundamentally cannot offer: SHA-256 has no dial to turn. It's exactly as fast today as it will be in ten years, while the hardware attacking it keeps getting faster every year, which is precisely backwards from what you want for something that needs to stay hard to crack over a long horizon.

Salting isn't the same fix as slowing down

A common point of confusion is that salting โ€” appending a unique random value to each password before hashing โ€” sounds like it solves the same problem bcrypt solves, and it doesn't, though it solves a real and separate problem. Without a salt, identical passwords hash to identical values, which means an attacker can precompute a lookup table (a rainbow table) mapping common passwords to their hashes once, then instantly reverse any hash in a stolen database that matches an entry in that table. A unique salt per password defeats precomputed tables entirely, because "password123" salted with one random value produces a completely different hash than the same password salted with another โ€” the attacker can no longer reuse work across different accounts, or across different breached databases.

What salting does not do is slow down an attack against any single password once the attacker has that password's specific salt, which they always do, because the salt is stored right alongside the hash โ€” it isn't a secret, it just has to be unique. A salted SHA-256 hash is immune to rainbow tables but still just as fast to brute-force one password at a time as an unsalted one; the attacker simply runs the same fast hash function with the known salt appended, guess after guess, at the same billions-per-second rate. Bcrypt salts automatically as part of its design (the salt is embedded in its own output format), so in practice a bcrypt hash gets both properties โ€” but the slowness and the salting are two independent defenses against two different attacks, and confusing them is exactly how "we already salt our hashes" ends up being offered as a defense against an attack that salting alone does nothing to stop.

Reading a bcrypt hash

A bcrypt output looks something like $2b$12$KIXQeI7C8n2Z1sVvWn8n0.LSjfDkV/rZhE7SjbBqAK2j8xY0FhOFa, and every segment is meaningful. $2b$ identifies the specific bcrypt variant in use (there have been a few over the years, mostly fixing subtle implementation bugs rather than changing the core algorithm). 12 is the work factor โ€” in this case, 212 internal rounds. The next 22 characters are the salt, and everything after that is the actual hash output. This is also why bcrypt hashes are self-contained and portable: unlike a raw SHA-256 hex digest, which tells you nothing about how it was produced, a bcrypt string carries its own salt and cost factor with it, so any correct bcrypt implementation can verify a login attempt without needing separate storage for salt or configuration.

Picking a work factor that ages well

The usual guidance is to pick the highest work factor that still keeps login latency acceptable on your actual production hardware โ€” commonly landing somewhere in the range of 10 to 14 for bcrypt as of the current generation of server hardware, with the number expected to creep upward over time as hardware improves. This isn't a set-once decision: a work factor chosen to take 250ms on today's hardware will take less time on tomorrow's, quietly eroding the protection without any code change being at fault. The practical fix is to check and re-hash on login โ€” when a user authenticates successfully, compare the work factor embedded in their stored hash against the current target, and if it's lower, re-hash their password (which you have in plaintext at that exact moment, and only that moment) with the current, higher work factor before storing it again. Done consistently, this means a database's protection level keeps pace with current hardware without ever requiring a bulk migration or forcing every user to reset their password at once.

Where Argon2 fits in

Bcrypt has one structural limitation worth knowing about: its memory usage is fixed and small, which means it can be attacked somewhat efficiently by custom hardware (FPGAs and ASICs) built specifically to run many bcrypt computations in parallel with dedicated silicon. Argon2 โ€” the winner of the 2015 Password Hashing Competition and now the generally recommended default for new systems โ€” addresses this by making memory usage a tunable parameter alongside time cost, deliberately requiring a large, configurable amount of RAM per hash computation. That matters because RAM is expensive to parallelize in custom hardware in a way that raw computation isn't; an attacker building specialized cracking hardware can cheaply stamp out thousands of parallel compute units, but can't cheaply give each of those units its own large bank of fast memory. For a new system with no existing bcrypt-hashed passwords to migrate, Argon2id (the hybrid variant balancing resistance to different attack types) is generally the better starting choice. For a system already using bcrypt correctly, with an appropriate and periodically-increased work factor, the practical difference is small enough that migrating isn't usually the most urgent item on the list.