How passwords are stored safely: never as plaintext. Learn the OWASP 2026 ranking of Argon2id, scrypt, bcrypt, and PBKDF2, plus why fast hashes fail badly.
In December 2009, the social app RockYou lost 32 million user passwords stored entirely in plaintext. The dump circulated freely and became the seed dictionary for password cracking tools for the next decade. That breach is the textbook example of how passwords are stored badly. The question of how passwords are stored safely has a straightforward answer: never store plaintext, and never store a fast hash. Store the output of a slow, memory-hard key derivation function with a unique random salt per user.
The OWASP Password Storage Cheat Sheet, current as of 2026, ranks Argon2id first, scrypt second, PBKDF2 for FIPS-140 compliance, and bcrypt for legacy systems. Fast hashes like SHA-256 are explicitly rejected for password storage because a single RTX 4090 computes about 22 billion SHA-256 hashes per second in hashcat.
This post walks through what safe password storage looks like, why fast hashes fail, and what parameters to use for each recommended function. You can hash a password with Argon2id or PBKDF2 using the PBKDF2 and Argon2 tool.
Safe password storage never keeps the password itself. The application receives the plaintext password at registration, runs it through a key derivation function, and stores only the resulting hash. At login, the application repeats the same function on the submitted password and compares the output to the stored hash. If they match, the password was correct. The plaintext is discarded immediately and never written to disk.
The hash must come from a slow, memory-hard function, not a general-purpose hash like SHA-256. Each password gets its own random salt, generated at registration, stored alongside the hash. The salt ensures that identical passwords produce different stored values and defeats precomputed rainbow tables. Optionally, a server-side pepper can be applied as a second input stored outside the database in an environment variable or hardware security module.
Login endpoints must also rate-limit attempts. Even with strong hashing, an attacker who can submit unlimited guesses will eventually find weak passwords. Rate limiting, account lockout thresholds, and progressive delays complement the hashing defense. You can test whether a given password resists guessing with the Password Strength Checker tool.
General-purpose hash functions such as SHA-256, SHA-1, and MD5 are designed to be fast. Speed is the enemy of password storage. The faster the function, the more guesses an attacker can test per second against a stolen hash.
A 2025 hashcat benchmark on a single NVIDIA RTX 4090 reports roughly 22 billion SHA-256 hashes per second, as documented in the hashcat forum benchmark thread. At that rate, an attacker testing a stolen unsalted SHA-256 hash against a dictionary of 10 million common passwords finishes in under a millisecond. Even random 8-character lowercase passwords, a space of about 208 billion combinations, fall in roughly 10 seconds on one GPU.
This is why OWASP explicitly lists MD5, SHA-1, and SHA-256 as unacceptable for password storage. They are fine for file integrity checks and digital signatures, where speed and collision resistance matter, but they are catastrophically wrong for passwords. The correct functions are deliberately slow and memory-hard so that each guess costs the attacker real time and real RAM.
Three major breaches illustrate the failure modes, and each failed for a different reason.
RockYou, 2009: 32 million passwords stored in plaintext. The Wikipedia article on RockYou documents the breach. The plaintext dump became the foundation of the rockyou.txt wordlist that ships with every password cracking toolkit. Plaintext storage means zero protection; a database read is total compromise.
LinkedIn, 2012: 6.5 million passwords hashed with unsalted SHA-1. The initial breach was bad, but the damage grew. In 2016, a larger dump of 167 million LinkedIn records appeared in what became known as the "Mother of All Breaches." Because the hashes were unsalted, attackers cracked them with rainbow tables and dictionaries within hours. The Wikipedia article on the 2012 LinkedIn hack covers the timeline. The lesson: a fast hash without a salt is barely better than plaintext once the database is stolen.
Adobe, 2013: 153 million encrypted passwords using 3DES in ECB mode, plus plaintext password hints stored alongside them. The Wikipedia article on the Adobe data breach describes the incident. ECB mode produces identical ciphertext blocks for identical plaintext blocks, which let researchers recover passwords by matching repeated ciphertext patterns. Storing the plaintext hints alongside the ciphertext made the cryptanalysis far easier. The lesson: reversible encryption is the wrong tool for passwords, and leaking hints turns a weak scheme into a broken one.
OWASP ranks four functions for password storage. The ranking reflects resistance to GPU and ASIC cracking, not just theoretical security.
Argon2id is the first choice. It won the Password Hashing Competition in 2015 and is defined in RFC 9106. OWASP recommends a minimum of 19 MiB memory, 2 iterations, and 1 degree of parallelism. Argon2id is memory-hard, which means each guess requires allocating significant RAM, making parallel GPU attacks expensive.
scrypt is the second choice, defined in RFC 7914. OWASP recommends parameters N=2^17, r=8, p=1. Like Argon2id, scrypt is memory-hard, though it is older and less tunable.
PBKDF2 is the third choice, recommended when FIPS-140 compliance is mandatory. It is defined in NIST SP 800-132. OWASP recommends 600,000 iterations using HMAC-SHA-256. PBKDF2 is not memory-hard, so GPUs can attack it faster than Argon2id or scrypt, but the high iteration count keeps it acceptable.
bcrypt is the fourth choice for legacy systems. It was designed by Niels Provos and David Mazières and published in their 1999 USENIX paper "A Future-Adaptable Password Scheme". OWASP recommends a work factor of 10 or higher. bcrypt is not memory-hard, and it truncates passwords longer than 72 bytes, so applications should pre-hash long inputs with SHA-256 before bcrypt.
| Function | Memory-hard? | OWASP rank | Recommended params | Standard |
|---|---|---|---|---|
| Argon2id | Yes | 1st | 19 MiB, 2 iterations, p=1 | RFC 9106 |
| scrypt | Yes | 2nd | N=2^17, r=8, p=1 | RFC 7914 |
| PBKDF2 | No | 3rd (FIPS) | 600,000 iterations, HMAC-SHA-256 | NIST SP 800-132 |
| bcrypt | No | 4th (legacy) | work factor 10+ | Provos & Mazières 1999 |
You can verify an existing bcrypt hash against a password with the bcrypt Hash Checker tool.
A salt is necessary but not sufficient. Salting defeats rainbow tables and makes identical passwords produce different hashes. It does not slow down a targeted attack on a single hash, because the attacker knows the salt and includes it in every guess. If the password itself is weak, a slow function plus a salt still loses to a determined attacker with a good dictionary.
Memory-hardness has its own tradeoffs. Argon2id at 19 MiB per hash means a server handling 1,000 concurrent logins needs roughly 19 GiB of RAM just for the hashing step. Tuning the parameters to your hardware matters; setting them too high creates a denial-of-service vector where an attacker floods the login endpoint.
A pepper helps if, and only if, it is stored securely outside the database. If the pepper lives in the same source repository or the same backup as the database, it provides no additional protection. Rate limiting and monitoring are also required, because no hashing function protects against unlimited online guessing attempts.
Safe password storage comes down to two rules: never store plaintext, and never store a fast hash. Use Argon2id with a per-user salt and parameters tuned to your server's memory budget, falling back to scrypt, PBKDF2, or bcrypt only when you have a specific constraint like FIPS-140 compliance or a legacy codebase. The breaches at RockYou, LinkedIn, and Adobe each failed one of these rules, and the fixes are well-documented in the OWASP cheat sheet. Hash a password with Argon2id and check the parameters yourself using the PBKDF2 and Argon2 tool.
Plaintext storage means a single database read exposes every account. The RockYou breach in 2009 dumped 32 million plaintext passwords, which then fueled cracking dictionaries for years. Safe storage keeps only the output of a slow, salted key derivation function, so a stolen database does not directly reveal any password.
OWASP recommends Argon2id as the first choice, defined in RFC 9106, with at least 19 MiB memory, 2 iterations, and 1 degree of parallelism. scrypt is the second choice. PBKDF2 with 600,000 iterations of HMAC-SHA-256 is acceptable when FIPS-140 compliance is required. bcrypt at work factor 10 or higher is used for legacy systems.
SHA-256 is designed to be fast, and speed helps the attacker. A single RTX 4090 computes about 22 billion SHA-256 hashes per second in hashcat, which means an unsalted SHA-256 password hash can be cracked with a dictionary in under a millisecond. Password hashing functions are deliberately slow and memory-hard to make each guess expensive.
Encryption is reversible: with the key, you recover the plaintext. Hashing is one-way: you cannot recover the password from the hash. Passwords should be hashed, not encrypted, because there is no legitimate reason to recover the original password. Encryption also failed Adobe in 2013, where reversible 3DES in ECB mode let researchers reconstruct passwords.
You need a salt. Every password must have its own unique random salt stored with the hash. A pepper is optional. It is a single secret shared across all passwords and stored outside the database. A pepper adds protection only if the database is stolen but the pepper is not, which requires careful key management.
PBKDF2 / Argon2 Hash Tool
Modern password hashing with PBKDF2 and Argon2 algorithms for secure key derivation.
bcrypt Hash Checker
Verify and analyze bcrypt password hashes for security validation and format checking.
Salt Generator
Generate cryptographic salts for secure password hashing and random number generation.
Password Strength Checker
Evaluate password strength and get security recommendations.
How SHA-256 Works: A Step-by-Step Walkthrough for Developers
SHA-256 is defined in NIST FIPS 180-4. This walkthrough explains padding, message schedule expansion, the 64-round compression function, and why you should never use SHA-256 for passwords.
The Difference Between Encoding, Encryption, and Hashing
Base64 is not encryption. This guide defines encoding, encryption, and hashing precisely, runs the same input through each, and explains when to use which in production systems.
What Is a Cryptographic Nonce? (And Why Reusing One Breaks Everything)
Sony reused a nonce in PS3 code signing and hackers extracted the master key. PuTTY had biased nonces in 2024. Here is what a nonce is and why reusing one destroys cryptographic security.