The one rule: never store the password itself
The foundational rule of password storage is simple: never keep the plaintext password. Instead of saving what the user typed, you save a one-way hash of it. A hash function takes any input and produces a fixed-length fingerprint that cannot be reversed back into the original. When someone logs in, you hash the password they submit and compare it against the stored hash. If they match, the password is correct — and at no point did you ever need to know, or keep, the real password.
This also means a related rule: do not "encrypt" passwords. Encryption is two-way by design — anyone holding the key can decrypt every stored value. That key becomes a single point of total failure. Hashing is one-way on purpose. You never need to recover a password, only to verify it, so reversibility is a liability, not a feature.
Why fast hashes like MD5 and SHA-256 are the wrong tool
It is tempting to reach for a familiar hash like MD5 or SHA-256. Both are one-way, so what's the problem? Speed. These functions were engineered to be fast — to fingerprint files and verify downloads at high throughput. For passwords, that speed works for the attacker, not you. A single consumer GPU can compute billions of SHA-256 hashes per second. If an attacker steals your hash table, they can simply hash every word in a dictionary and every common password until something matches, all in minutes.
Worse, because the same input always produces the same fast hash, attackers precompute enormous lookup files called rainbow tables: every plausible password mapped to its hash in advance. With a rainbow table, cracking is reduced to a lookup. Plain fast hashes give you essentially no protection for low- and medium-entropy passwords, which is most of them.
Salt: a unique random value per user
A salt is a random value generated separately for each user and combined with the password before hashing. Two users who both choose password123 will have different salts, so their stored hashes are completely different. This defeats rainbow tables outright — a precomputed table would have to be regenerated for every individual salt, which is infeasible — and it hides the fact that two accounts share a password.
A common point of confusion: the salt is not secret. You store it right alongside the hash in the database, often in the same field. Its job isn't secrecy; it's uniqueness. Each salt should be long (16 bytes or more) and generated from a cryptographically secure random source. In practice, modern password libraries generate the salt for you and embed it directly in the output string, so you store a single value and never manage salts by hand.
Pepper: an optional app-wide secret
A pepper is an extra secret value mixed into every password before hashing — but unlike a salt, it is the same for the whole application and is kept outside the database (for example in an environment variable, a key vault, or an HSM). The idea is that an attacker who steals only the database, but not the pepper, still cannot crack the hashes. It's a defense-in-depth layer, not a replacement for salting, and it adds operational complexity around key storage and rotation. Treat it as an optional bonus once the fundamentals are in place.
Slow KDFs: the actual answer
Salting stops rainbow tables, but it does nothing about raw brute-force speed. The real fix is to make hashing deliberately slow. Purpose-built password hashing functions — properly called key derivation functions (KDFs) — take a work factor (sometimes called cost or iterations) that you tune so a single hash takes a meaningful fraction of a second. That's invisible to a user logging in once, but it turns an attacker's "billions per second" into a painful crawl. As hardware gets faster, you raise the work factor to keep pace.
The best modern KDFs are also memory-hard: they require a large amount of RAM per hash, which neutralizes the cheap parallelism of GPUs and custom ASICs that makes fast hashes so dangerous. Argon2id is the current recommended default. bcrypt and scrypt are also strong, mature choices. PBKDF2 is acceptable where a certified algorithm is required, though it is not memory-hard.
| Algorithm | Speed | Memory-hard | Use for passwords? |
|---|---|---|---|
| MD5 | Very fast | No | No — also broken for collisions |
| SHA-256 | Very fast | No | No — far too fast |
| PBKDF2 | Tunable (slow) | No | Acceptable if certification requires it |
| bcrypt | Tunable (slow) | Limited | Yes — solid, well-tested |
| scrypt | Tunable (slow) | Yes | Yes |
| Argon2id | Tunable (slow) | Yes | Yes — current recommendation |
Crucially, do not implement these yourself. Use a well-reviewed library binding for your language, pass it the user's password, and store the single self-describing string it returns — it encodes the algorithm, the work factor, and the salt together, which also makes future upgrades painless.
The other essentials
Correct hashing is the core, but a few practices round out safe password handling:
- Compare in constant time. When verifying, use a constant-time comparison so the time taken doesn't leak information about how much of the hash matched. Good KDF libraries already do this in their
verifyfunction. - Favor length over complexity. A long passphrase is far stronger than a short string of mixed symbols. Allow long passwords (and spaces), and drop arbitrary composition rules that just push users toward predictable patterns.
- Rate-limit and lock out. Throttle repeated login attempts per account and per IP so online guessing is impractical even before the hashing slows attackers down.
- Offer MFA. Multi-factor authentication means a stolen or cracked password alone isn't enough to take over an account.
- Never log or email the plaintext. If a system can email you your existing password, it stored it reversibly — a clear red flag.
If you're choosing or evaluating passwords, the password strength checker shows how resistant a password is to guessing, the password generator creates strong random ones, and the hash generator lets you see how hashing transforms an input.
The takeaway
Storing passwords safely comes down to one defensive idea: keep something that proves the user knows the password, never the password itself, and make verifying it cheap for you but ruinously expensive for an attacker. A unique salt plus a slow, memory-hard KDF like Argon2id does exactly that. Get those two things right, lean on a trusted library instead of custom code, and a stolen database becomes a footnote instead of a disaster.
Frequently asked questions
- Why can't I just store passwords with MD5 or SHA-256?
- MD5 and SHA-256 are designed to be fast, which is exactly wrong for passwords. Modern hardware can compute billions of these hashes per second, so an attacker who steals your database can brute-force common passwords almost instantly and reuse precomputed rainbow tables. Password storage needs a deliberately slow algorithm, not a fast one.
- What is a salt and where do I store it?
- A salt is a unique, random value generated per user and combined with the password before hashing. It ensures two users with the same password get different hashes and makes precomputed rainbow tables useless. The salt is not secret — store it alongside the hash in the database. Modern password hashing libraries generate and embed the salt for you automatically.
- Should I encrypt passwords instead of hashing them?
- No. Encryption is reversible: anyone with the key can recover the original password, so a single leaked key exposes every account. Hashing is one-way by design — you never need to recover the password, only to verify it by hashing the input and comparing. Always hash passwords with a slow KDF; never encrypt them.
- Which password hashing algorithm should I use?
- Argon2id is the current recommendation because it is memory-hard, which resists GPU and ASIC cracking. bcrypt and scrypt are also solid, well-tested choices. PBKDF2 is acceptable where it is the only certified option available. Avoid plain MD5, SHA-1, or SHA-256 for passwords entirely, and always use a well-reviewed library rather than rolling your own.
This guide is general educational information about defensive password storage, not security advice for a specific system. Follow current guidance from a trusted source such as OWASP and have authentication code reviewed by a qualified professional.