DevTools Hub

Search tools

Search for a developer tool

Why SHA-256 Should Not Be Used for Passwords

Part of the Hashing Toolkit

SHA-256 is not broken. No collision has ever been found, no practical preimage attack exists, and it's the same algorithm securing TLS certificates, Git commits, and Bitcoin itself. That's precisely why it keeps ending up in password storage code — and precisely why it's the wrong tool there anyway. "Cryptographically secure" and "safe for password storage" are different questions, and SHA-256 answering yes to the first says nothing about the second.

The question SHA-256 was never built to answer

SHA-256 was designed to be a fast, collision-resistant fingerprint of arbitrary data — exactly what you want when hashing a Git commit or a file checksum, where computing millions of hashes per second is the entire point. Password storage needs the opposite property. When an attacker steals a password database, the hash function isn't protecting data in transit or verifying integrity — it's the only thing standing between the attacker and every plaintext password, and they get to attack it completely offline, with no rate limit, no lockout, and no one watching. Speed that's a feature everywhere else becomes the attacker's biggest advantage here.

How fast is too fast?

A single RTX 4090 — an off-the-shelf consumer GPU — computes raw SHA-256 at roughly 21 billion hashes per second in hashcat. At that rate, an 8-character password drawn from upper/lowercase letters and digits — about 218 trillion possibilities — falls in under three hours, worst case, on one GPU. Add a few more and it falls in minutes. And that's brute force against every possible combination; real attacks are faster still, because real passwords cluster around dictionary words, names, and predictable patterns that wordlist and rule-based attacks target directly instead of searching blindly.

SHA-256 also happens to be the exact hash function Bitcoin mining runs, which means the hardware built to mine it is public, commercially available, and staggeringly fast. A single modern Bitcoin ASIC — the Bitmain Antminer S21 XP — computes SHA-256 at 270 trillion hashes per second. That's roughly 12,700 times faster than the consumer GPU above, in a single purpose-built chip you can buy today. If your password hash is a bare SHA-256 digest, the same silicon mining Bitcoin can attack it at full speed.

Salting doesn't fix this

A per-user salt is necessary — without one, identical passwords produce identical hashes, and an attacker can precompute a rainbow table once and reuse it against every stolen database. But a salt doesn't touch the number above. A salted SHA-256 hash computes in exactly the same nanoseconds as an unsalted one; the attacker just has to run the same 21-billion-per-second attack once per account instead of once for the whole database. Salting solves "attack everyone at once," not "attack one account fast." See Password Hashing Explained for the full breakdown of what salting actually does and doesn't do.

Hashing it twice doesn't fix it either

SHA256(SHA256(password)), or adding a hardcoded "pepper" string before hashing once, are common instincts — and neither meaningfully helps. Two rounds instead of one is a 2× slowdown, not the many-orders-of-magnitude slowdown an attacker with 21 billion guesses per second actually requires. It also invites hand-rolled crypto bugs (peppers stored next to the code that uses them, homemade multi-round schemes with no security review) in exchange for essentially nothing. If the goal is "apply SHA-256 enough times that it actually matters," there's already a standard, audited way to do that — which raises an obvious question.

Wait — doesn't PBKDF2 use SHA-256 internally?

Yes, and this is exactly where the misconception falls apart usefully. OWASP recommends PBKDF2-HMAC-SHA256 at 600,000 iterations as an acceptable password hashing choice — built entirely out of SHA-256. The difference between that and a naive SHA256(password + salt) isn't the algorithm. It's the 600,000. Take that RTX 4090's 21-billion-per-second raw SHA-256 rate and divide it by 600,000 iterations per guess, and the attacker's effective rate drops to roughly 35,000 guesses per second — the same GPU, the same underlying hash function, six orders of magnitude slower, purely because the iteration count is baked into the algorithm's definition rather than left to chance. That mandated, tunable, hard-to-skip repetition is the entire difference between "uses SHA-256" being fine and being a vulnerability — see Password Hash Generator to compute a real PBKDF2-HMAC-SHA256 hash and see those iterations in the output.

If you already have SHA-256 passwords in production

This happens, and it's fixable without necessarily forcing every user to reset their password immediately:

  • Wrap immediately, migrate later. Run a batch job that stores bcrypt(existingSha256Hash) for every row, and change verification to hash the login attempt with SHA-256 first, then bcrypt-compare against the wrapped value. This needs no plaintext passwords and can run as a one-off migration tonight — it immediately gets every account behind a slow hash, even before anyone logs in again.
  • Rehash properly on next login. You have the real plaintext password in memory at the moment of a successful login — that's the opportunity to compute a proper bcrypt or Argon2id hash from scratch and replace the wrapped value. Combine this with the step above and active accounts migrate to a real, unwrapped password hash within days or weeks, with inactive accounts left safely wrapped until they either log in or get reset.
  • Force a reset only as a last resort for accounts that never come back — after a reasonable window (many teams use 6–12 months), or immediately if you have any reason to think the SHA-256 hashes have already been exposed.

What to use instead

bcrypt, scrypt, or Argon2id — deliberately slow, tunable algorithms built for exactly this job, covered in full in Password Hashing Explained and compared head-to-head in Bcrypt vs Argon2. PBKDF2-HMAC-SHA256, at OWASP's recommended iteration count, is also acceptable — it just isn't the same thing as calling SHA-256 by itself.

Try it yourself

Paste a bare SHA-256 hash into Password Hash Inspector and it's flagged as a critical finding automatically — no salt, no cost factor, nothing to slow an attacker down. Password Hash Generator builds real bcrypt, scrypt, Argon2id/i/d, and PBKDF2 hashes instead, with the parameters this post describes right there in the output. Both run entirely in your browser.

Related tools