Storing passwords properly: hashing explained
Day 8 of our web security series: why sites should never be able to read your password, what hashing and salting actually do, and how to tell if a site gets it right.
Welcome to week two: logins and accounts. Last week was about how a page reaches you safely. This week is about the moment you prove who you are, starting with a question most people never think about: what does a website actually do with your password after you type it?
The honest answer should be: almost nothing. A well-built site never stores your password at all. Today is about how that’s possible, and why it matters so much when things go wrong.
Assume the database will leak
Security people plan for the bad day. Databases get stolen through bugs, misconfigured backups, leaked admin passwords or a compromised supplier. It happens to large, well-funded companies every year.
So the real question isn’t “will anyone ever see our user table?” It’s “when someone does, what will they get?”
If passwords are stored as plain text, they get everything. And because many people reuse passwords, a leak from one small site becomes access to email, shopping and banking accounts elsewhere. That chain reaction is how most account takeovers start.
Encryption isn’t the answer
A common reply is “we encrypt the passwords”. It sounds reassuring, but encryption is designed to be reversible. Whoever has the key can turn every password back into readable text. And the key usually lives on the same server as the database, so the same breach often exposes both.
A site doesn’t need to know your password. It only needs to check that what you typed matches. That calls for something that can’t be reversed.
Hashing: a one-way fingerprint
A hash function takes any input and produces a fixed-length fingerprint:
password123 -> ef92b778bafe771e89245b89ecbc08a4...
password124 -> 33631376724e5d5480fa397dfcf03b66...
Three properties make it useful:
- Same input, same output. Type the same password, get the same fingerprint.
- Tiny change, totally different output. One character changes everything.
- One way. There’s no “decode” button. You can’t get from the fingerprint back to the password.
So at sign-up, the site stores only the fingerprint. At login, it hashes what you typed and compares fingerprints. If they match, you’re in. Your actual password is never written down.
Why plain hashing still isn’t enough
If hashing were the whole story, this would be a short post. Two problems remain.
Identical passwords give identical hashes. If a thousand users chose password123, the leaked table shows a thousand identical fingerprints. Attackers also keep huge precomputed lists of common passwords and their hashes, so popular passwords are recognised instantly.
Fast hashes are fast for attackers too. General-purpose hashes like SHA-256 are built for speed. Modern hardware can try billions of guesses per second against them, which means weak and medium passwords fall quickly.
Salt: making every hash unique
A salt is a random value generated for each user and stored next to their hash. The site hashes the password together with the salt:
user A: salt = 9f2c... hash(9f2c... + password123) -> 4a1d...
user B: salt = e71b... hash(e71b... + password123) -> c03f...
Now two people with the same password have completely different fingerprints, and precomputed lists are useless. An attacker has to attack every account separately.
The salt isn’t secret. Its job is to be unique, not hidden.
Slow on purpose: password hashing algorithms
The second fix is to use an algorithm designed to be deliberately slow and memory-hungry. Checking one password at login takes a fraction of a second, which nobody notices. But for an attacker trying billions of guesses, that slowness multiplies into years.
The algorithms built for this are:
- Argon2id, the current first choice for new systems.
- scrypt, also memory-hard and widely supported.
- bcrypt, older but still a solid choice and available almost everywhere.
- PBKDF2, acceptable when required for compliance, with a high iteration count.
They all handle salting for you. What you should not use for passwords: plain MD5, SHA-1 or SHA-256, with or without a salt, and anything home-made.
Why it matters for security
Good password storage doesn’t stop a breach. It decides how bad the breach is. With Argon2id or bcrypt and unique salts, a stolen table gives attackers a slow, expensive job, and strong passwords may never be cracked at all. That buys time for the site to notice, tell users and force resets.
With plain text or a fast hash, the same breach becomes a list of working passwords within hours.
Clues that a site gets it wrong
As a user you can’t see a site’s database, but some signs are telling:
- “Forgot password” emails you your current password. That means they can read it. A good site sends a reset link instead.
- A maximum password length that’s oddly short, like 12 or 16 characters, or a ban on certain symbols. Hashing doesn’t care about length or characters, so strict limits can hint at old storage.
- Support staff can tell you your password. They should never be able to.
What you can do today
If you use websites:
- Use a different password for every site. A password manager makes this painless and limits any leak to one account.
- Turn on two-factor login for important accounts, so a leaked password alone isn’t enough.
- Check whether your email appears in known breaches with our breach check tool, and change any reused passwords it turns up.
- Treat any site that emails you your actual password as untrustworthy, and don’t reuse that password anywhere.
If you run a website:
- Find out exactly how your system stores passwords. If you use a framework’s built-in login, check which algorithm it’s configured for.
- Use Argon2id or bcrypt through a well-maintained library. Never write your own hashing.
- If you have old hashes, upgrade them: rehash each user’s password with the new algorithm the next time they log in.
- Make sure passwords never end up in logs, analytics or error reports. Read the OWASP Password Storage Cheat Sheet for current settings.
Coming up next
On Day 9 we’ll look at the login page itself: how sites resist password guessing with rate limits and lockouts, without locking out the real user.