# 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 JWTs** — `HS256` 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](/tools/security/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](/tools/security/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.
