devkult_
tools24converters26
home/blog/why-not-sha256-for-passwords
securitypasswordsbcrypt

Why You Shouldn't Hash Passwords with SHA-256

SHA-256 is an excellent cryptographic hash. It's fast, collision-resistant, and the right tool for checksums, signatures, and content addressing. It is also exactly the wrong tool for storing passwords — and the reason is the same property that makes it good everywhere else: speed.

The threat model for password storage

When you store a password, you're planning for the day your database leaks. Assume the attacker has your full table of hashes. The only question that matters then is: how expensive is it to recover the original passwords by guessing?

With a fast hash like SHA-256, the answer is "cheap." A commodity GPU can compute billions of SHA-256 hashes per second. An attacker takes a wordlist of common passwords, hashes each one, and compares against your table. Most user passwords aren't random, so a large fraction fall in hours.

Speed is a feature when you're verifying a file. It's a liability when each guess an attacker makes is nearly free.

Salting helps, but doesn't fix the core problem

A salt is a unique random value stored alongside each hash and mixed into it. Salting is mandatory — it stops precomputed "rainbow table" attacks and ensures two users with the same password get different hashes. But salting does not slow down an attacker who's targeting one specific hash. They still get billions of guesses per second against that salted hash. Salt defeats batch precomputation; it does nothing about raw guessing speed.

What bcrypt does differently

Bcrypt (and its peers scrypt, Argon2, and PBKDF2) are deliberately slow password-hashing functions. Two properties matter:

  1. A tunable work factor. Bcrypt takes a "cost" parameter — each increment doubles the work. You pick a cost that makes a single hash take, say, ~250ms on your server. That's invisible to a user logging in once, but it caps an attacker at a handful of guesses per second instead of billions.
  2. Built-in salting. Bcrypt generates and stores the salt inside the hash string itself, so you don't manage it separately.

A bcrypt hash encodes everything needed to verify it later:

$2b$12$R9h/cIPz0gi.URNNX3kh2OPST9/PgBkqquzi.Ss7KIUgO2t0jWMUW
└┬┘└┬┘ └────────────────────┬─────────────────────────────┘
 │  │                        └─ salt (22 chars) + hash
 │  └─ cost = 12 (2^12 rounds)
 └─ algorithm version

Because the cost is baked into the hash, you can raise it over time: new passwords use the higher cost, and you re-hash old ones on next login. Hardware gets faster; your work factor gets to keep up.

So when is SHA-256 the right call?

SHA-256 is great for everything passwords aren't:

  • File integrity / checksums — verifying a download wasn't corrupted or tampered with.
  • Digital signatures and JWTsHS256 is HMAC-SHA256; here speed is fine because security rests on a secret key, not on resisting guessing.
  • Content addressing / deduplication — naming a blob by its hash.

For any of those, generating a hash quickly is exactly what you want — try the hash generator, which computes SHA-1/256/384/512 locally in your browser.

Practical takeaways

  • Never store passwords with a fast, general-purpose hash (MD5, SHA-1, SHA-256), salted or not.
  • Use a slow, tunable password hash — bcrypt, scrypt, or (preferably, for new projects) Argon2id.
  • Pick a work factor that costs ~200–300ms on your hardware, and revisit it yearly.
  • You don't manage salts manually with bcrypt — they're embedded in the output.

To see the work factor in action, the bcrypt tester lets you hash a string at different cost values and verify a password against an existing hash, so you can feel how much each cost increment slows things down.

The one-line rule: hash files fast, hash passwords slow.

More from the blog