You should never store a password as-is in the database. So how do you store it? The answer is hashing. Here's how it works and the key points for doing it safely (no attack steps).
Hashing ≠ encryption
These get confused, but the goals are opposite. Encryption is "reversible with a key, so you can read it back later." Hashing is "one-way — you can't reverse it." A password is information even the operator shouldn't need to read back, so a deliberately irreversible hash is the right fit.
Encryption (reversible)
plaintext ⇄ ciphertext. key reverses it. for data you read back
Hashing (one-way)
password → hash. can't be reversed. for storing passwords
At login, you check "hash the entered password the same way and see if it matches the stored hash." You never need to recover the original.
Why "raw MD5/SHA-256" isn't enough
MD5 and SHA-256 are fast hashes. That's a useful property elsewhere, but a weakness for password storage: an attacker can compute a huge number of guesses per second against a leaked hash.
- Rainbow tables: a giant precomputed table of "common password → its hash." With a raw hash, a match is found just by looking it up.
- Brute force: the faster the hash, the more guesses per unit time — i.e. the easier to crack.
General-purpose hashes vs password hashes
Both are called "hashes," but they were built for different jobs. Only the right-hand column belongs in password storage.
| General-purpose hash (MD5, SHA-1, SHA-256) | Password hash (Argon2id, bcrypt, scrypt, PBKDF2) | |
|---|---|---|
| Built for | Detecting file tampering, signatures, etc. | Storing passwords |
| Speed | As fast as possible | Deliberately slow (tunable) |
| Salt | Not part of the design | Part of the design (PBKDF2 takes it from the caller) |
| Tuning the cost | Not possible | Cost, iterations, or memory; raised over time |
| For password storage | Don't use | Use |
NIST SP 800-63B-4 likewise requires passwords to be stored with a password hashing scheme that takes a salt and a cost factor as inputs, with the cost set as high as practical without hurting performance and increased over time.
The two safeguards
Add a salt (a different value per user)
Before hashing, mix in a random value that differs per user (a salt). Now the same password stores differently for everyone, which defeats rainbow tables and stops one cracking run from covering reused passwords across accounts.
Use a deliberately slow, purpose-built hash
bcrypt / Argon2 / scrypt let you make the computation intentionally expensive (a cost parameter). A slowness no one notices for a single legitimate login drops the attacker's brute force to an impractical rate. Salting is built into these too.
Real cases: when a breach notice says "hashed"
Breach announcements sometimes say whether passwords were stored in plaintext, hashed, or encrypted. Here are cases this site has covered, arranged by storage method.
| Case | What was disclosed about passwords | What it means for users |
|---|---|---|
| Takufile-bin (2019) | Stored in plaintext | Readable as-is. Other services using the same password are at risk too |
| Sakura Internet (2026) | Some initial passwords were not hashed | Anyone still using an initial password needs to change it now |
| Gyazo (2026) | Password hashes leaked; method not disclosed | May be reversible depending on the method. The company asked users to change passwords |
Two things to keep in mind when reading a notice:
- "Hashed" alone doesn't tell you whether it's safe. Raw MD5 and a salted slow hash differ enormously in how easily passwords can be recovered
- If the method isn't stated, act as if it was weak. Change the password on that service and on any other service where you use the same one
You can check whether your email address appears in past breaches with how to check if your password was leaked.
How to check your own site and accounts
As a user: watch how password reset works
If "forgot password" emails you your original password, the service is very likely storing it in plaintext or a reversible form. Don't use a password there that you use anywhere else.
As a user: check your password's strength
Hashing protects long, hard-to-guess passwords best. Check a rough strength estimate with the password strength checker, and leave storage to a password manager.
As a developer: check which function stores passwords
Find the functions your code uses to store and verify passwords. PHP's password_hash(), Django's built-in auth, or a bcrypt/Argon2 library are password-specific and cover the basics. Any direct call to md5(), sha1(), or sha256 is a migration target.
As a developer: check stored values and settings
How to identify stored hashes, OWASP's minimum settings, and how to migrate off weak algorithms are covered in how to store passwords safely.
This site's view: don't build it yourself
Assuming "MD5 plus a salt is enough" is a common mistake. Safe password storage also depends on generating and storing the salt, tuning the cost, and handling timing differences. Rather than assembling that yourself, lean on your language/framework's official password function (most use bcrypt/Argon2 under the hood). For new systems, make Argon2id your first choice.
Sources (primary)
- NIST, "SP 800-63B-4 Digital Identity Guidelines: Authentication and Authenticator Management" (August 2025) — pages.nist.gov (hashing schemes that take a salt and a cost factor; raising the cost over time)
- OWASP, "Password Storage Cheat Sheet" — cheatsheetseries.owasp.org (hashing vs encryption, recommended algorithms, work factor)
- RFC 9106, "Argon2 Memory-Hard Function for Password Hashing and Proof-of-Work Applications" — rfc-editor.org
- Sources for each case (the companies' own announcements) are listed in the linked articles on this site
Read next
- Glossary: what a salt is (the random per-user value that defeats reuse attacks)
- Learn: how to store passwords safely (hashing + salt in practice)
- Basics: storing passwords the right way (stop plaintext) / choosing a password manager
- Case: the Takufile-bin data leak (2019) (plaintext password storage in the wild)