Why Websites Shouldn't Know Your Password

When you log in, a well-built site doesn't store your password. It stores a scrambled fingerprint of it, called a hash. At login, it hashes what you typed and compares fingerprints.

The reason is simple: databases leak. When they do, attackers shouldn't be able to read everyone's passwords. Whether they can depends almost entirely on how the passwords were hashed, and that comes down to some simple math.


🎯 Quick Answer (30-Second Read)

  • Hashing is one-way: easy to compute, practically impossible to reverse. Encryption is two-way, so it's the wrong tool for passwords.
  • Attackers don't reverse hashes, they guess: hash billions of candidate passwords and look for matches.
  • Salts (random values per user) stop attackers from cracking everyone at once.
  • Fast hashes (MD5, SHA-256) are bad for passwords because speed helps the attacker.
  • Use a slow, memory-hard hash: OWASP recommends Argon2id (min 19 MiB memory, 2 iterations, 1 thread), with scrypt, bcrypt (cost 10+) or PBKDF2 (600,000+ iterations) as alternatives.

Hashing vs Encryption

Encryption (two-way)
Data + key → ciphertext → back to data with the key. If the key leaks, every password leaks.
Hashing (one-way)
Password → fixed-length fingerprint. There's no key and no way back. You can only check a guess.

How Login Works With Hashing

sequenceDiagram participant U as User participant S as Server participant DB as Database Note over U,DB: Sign-up U->>S: password = "mango42" S->>S: salt = random 16 bytes S->>S: hash = Argon2id(password + salt) S->>DB: store salt + hash (never the password) Note over U,DB: Login U->>S: password = "mango42" S->>DB: fetch salt + hash S->>S: Argon2id(typed password + salt) S-->>U: match? → logged in

The Attacker's Math

After a breach, an attacker has hashes, not passwords. Their only move is guessing: hash a candidate, compare, repeat. So the question is how many guesses per second they can make, and how many guesses they need.

Number of possible passwords = (characters available) ^ (length)

Password type Possibilities
8 lowercase letters 26⁸ ≈ 209 billion
8 chars, upper + lower + digits 62⁸ ≈ 218 trillion
12 chars, upper + lower + digits 62¹² ≈ 3.2 × 10²¹

Time to try every option = possibilities ÷ guesses per second.

Fast hashes like MD5 and SHA-256 were designed for speed: modern GPUs compute billions of them per second. At 10 billion guesses per second, all 209 billion 8-letter lowercase passwords take about 21 seconds.

A deliberately slow password hash changes the denominator. If each guess costs a few thousand times more work, the same search takes hours or days instead of seconds, and the attacker's electricity bill explodes.

21 seconds
at 10 billion guesses/s (fast hash on GPUs)
5.8 hours
at 10 million guesses/s
~8 months
at 10,000 guesses/s (slow hash)

Time to try all 209 billion 8-letter lowercase passwords, for illustration. Slowing each guess 1,000,000× turns seconds into months.

Key takeaway: you can't make guessing impossible, but you can make each guess expensive. Your login check runs once per user. The attacker's runs billions of times.

In reality, attackers try likely passwords first (leaked password lists, common words, "Password@123"), so weak passwords fall fast no matter what. Slow hashing protects the reasonable passwords.


Salts: Stopping Mass Cracking

Without salts, everyone with the password iloveyou has the same hash. Attackers precompute hashes of common passwords once (in "rainbow tables") and crack every matching account instantly.

A salt is a random value generated for each user and mixed in before hashing:

User Password Salt Hash
Asha iloveyou 9f2c… a71e…
Ravi iloveyou 41bd… c03f…

Same password, different hashes. Precomputed tables become useless, and the attacker must crack each user separately. Salts aren't secret. They're stored next to the hash.


Why Memory-Hard Hashing Matters

GPUs and custom chips are very good at running many simple calculations in parallel. Argon2id and scrypt make each guess need a chunk of memory (OWASP's minimum for Argon2id is 19 MiB per hash). Memory is expensive to multiply across thousands of parallel guesses, so attackers lose much of their hardware advantage.


OWASP's Recommended Settings

Algorithm Minimum setting When to use
Argon2id 19 MiB memory, 2 iterations, parallelism 1 First choice
scrypt N = 2¹⁷, r = 8, p = 1 If Argon2id isn't available
bcrypt Work factor 10+ (72-byte password limit) Legacy systems
PBKDF2 600,000+ iterations, HMAC-SHA-256 When FIPS-140 compliance is required

In Python, for example:

from argon2 import PasswordHasher   # pip install argon2-cffi

ph = PasswordHasher()               # library defaults exceed OWASP's minimum
stored = ph.hash("mango42")         # salt is generated and embedded automatically

ph.verify(stored, "mango42")        # returns True, raises an error if wrong

Checklist for Developers

  • Never store plain-text or encrypted passwords
  • Never use MD5, SHA-1 or plain SHA-256 for passwords
  • Use Argon2id (or the alternatives above) via a well-known library
  • Let the library handle salts
  • Rate-limit login attempts and support two-factor authentication
  • Re-hash with stronger settings when users next log in

Frequently Asked Questions

Why is SHA-256 bad for passwords?

SHA-256 is secure as a general hash, but it's designed to be fast. Speed lets attackers test billions of guesses per second with GPUs. Password hashes like Argon2id, scrypt and bcrypt are deliberately slow and tunable, which makes guessing far more expensive.

Is bcrypt still safe?

bcrypt with a work factor of 10 or more is still acceptable, and OWASP lists it for legacy systems. It has a 72-byte password limit and isn't memory-hard, so new projects should prefer Argon2id where available.

What is a salt in password hashing?

A salt is a random value generated for each password and combined with it before hashing. It makes identical passwords produce different hashes, which defeats precomputed rainbow tables and forces attackers to crack each account separately. Salts are stored alongside the hash.


Conclusion

Password security is a numbers game. Attackers can't reverse hashes, so they guess, and the cost of each guess decides how long your users stay safe. Salts stop mass cracking, slow memory-hard hashes like Argon2id make each guess expensive, and good libraries handle both for you.


Sources

Last verified: 14 October 2026.


Related reads: Fix Supabase RLS Policy Error · Rate Limiter Explained · How Does Face ID Work?

I explain the tech behind everyday products. Follow on LinkedIn or X, or invite me to speak.