Every major password breach in history had one thing in common: the company stored passwords wrong. Here's the technique that makes stolen databases useless to attackers.

What's actually happening here?

When you create an account and set a password, the server immediately forgets what you typed. Instead it runs your password through a one-way mathematical function called a hash — producing a fixed-length string of characters that looks like random noise. Only that fingerprint gets stored. When you log in next time, the same function runs on whatever you typed, and the two fingerprints are compared. If they match, you're in. Your actual password never gets saved anywhere.

The problem this solves

Databases get breached. It happens to startups, it happens to banks, it happens to governments. If a company stores your actual password and their database is stolen, the attacker now has your password — and because most people reuse passwords, they can log into your Gmail, your bank, your everything. Storing only a hash means a stolen database is a pile of useless fingerprints, not a pile of passwords.

How it really works (step by step)

Registration — what happens when you set a password:

  1. You submit your password — travels over HTTPS so it's encrypted in transit.

  2. Server generates a random salt — a unique random string, different for every single user account. Typically 16–32 bytes of randomness.

  3. Salt is combined with your password — concatenated: salt + password or password + salt, depending on the implementation.

  4. Hash function runs on the combined string — produces a fixed-length fingerprint. SHA-256 always produces 64 hex characters regardless of input length.

  5. Database stores only: {salt, hash} — your actual password is gone. Not encrypted. Not hidden. Gone.

Login — what happens when you enter your password:

  1. Server retrieves your salt from the database — salt is not secret, it's stored in plain text alongside the hash.

  2. Same hash function runs on salt + what you typed — produces a new fingerprint.

  3. Fingerprints are compared — if they match, access granted. If not, rejected. The server never knew your password either time.

The part most tutorials skip

MD5 and SHA-256 are the wrong hash functions for passwords — even with salt. They were designed to be fast. A modern GPU can compute 10 billion SHA-256 hashes per second. That means an attacker with a stolen database can try 10 billion password guesses per second per GPU. The correct password hashing functions — bcrypt, scrypt, and Argon2 — are deliberately engineered to be slow. bcrypt takes ~100ms per hash by design. That drops an attacker's throughput from 10 billion/second to 10/second. Same stolen database, same hardware — bcrypt buys your users months of protection instead of minutes.

The work factor is configurable. As hardware gets faster every year, you increase the cost parameter so bcrypt stays slow on new hardware. This is called key stretching.

Real company doing this right now

LinkedIn's 2012 breach is the clearest lesson in what not to do. 6.5 million password hashes were leaked — and they were unsalted SHA-1 hashes. Within hours, the majority were cracked using pre-computed rainbow tables. The real damage came six years later in 2016 when it emerged the full breach was 117 million accounts. Because the hashes were fast and unsalted, most passwords were recovered within days of the breach. LinkedIn's users had their credentials used in credential stuffing attacks across the internet for years. After the breach, LinkedIn moved to salted bcrypt. The lesson cost them their users' trust and years of security fallout.

What breaks at scale?

The bcrypt bottleneck at high login throughput. If bcrypt takes 100ms per hash and you have 100,000 users logging in simultaneously, you need dedicated CPU capacity just for password hashing — it cannot be offloaded to a GPU (bcrypt is deliberately GPU-resistant). Large systems handle this by keeping a dedicated password-hashing service with its own autoscaling pool, separated from general API servers. They also rate-limit login attempts aggressively — not just for security, but because each failed login attempt burns 100ms of CPU.

The "aha" moment

Salt doesn't make the hash harder to crack — it makes every hash unique. Without salt, if two users have the same password, they have the same hash. An attacker cracks one and gets all of them. With salt, identical passwords produce completely different hashes. Every account must be attacked individually.

Your practical takeaway

  • Never use MD5, SHA-1, or SHA-256 alone for password storage — use bcrypt, scrypt, or Argon2id. Every major language has a battle-tested library for these. In Python it's bcrypt or passlib. In Node.js it's bcrypt or argon2. Use the library, don't implement it yourself.

  • Set your bcrypt work factor so hashing takes 100–300ms on your current hardware — then revisit it every 2 years as hardware improves. Most frameworks default to a work factor that was calibrated years ago on older hardware.

  • Rate limit login endpoints immediately — max 5 attempts per account per 15 minutes, with exponential backoff after that. This is your first line of defence before the hashing even matters.

Lesson 05 · Stage 1 — Security Foundations · System Design Made Easy