A password database should never contain readable passwords. It should contain verification records that let the server check a login while making a stolen copy expensive to attack.
Modern applications do this with a unique salt and an adaptive password hashing function. Salting does not strengthen a weak password, and hashing does not make a breach harmless, but the right design can slow offline guessing and limit how much one cracked password helps with other accounts.
Salt, hash, encryption, and pepper are often used as if they were interchangeable. They are not.
A salt is public random data that changes each password record. A password hash is a one-way derived value used for verification. Encryption is reversible with a key. A pepper is an additional secret held separately from the database. Those differences matter when a server or backup is exposed.
Why identical passwords need unique salts
Suppose two accounts choose the same password. If the application hashes the password alone, the two rows produce the same digest. An attacker can spot reused passwords and calculate a guess once, then compare it with every row. Precomputed lookup tables make that shortcut even easier.
A unique random salt breaks the pattern created by reused passwords. The server generates a fresh salt for each account and combines it with the password before running the password hashing function.
Identical passwords now produce different stored values because their salts differ. An attacker who steals the database must test a password guess against each salt separately. The salt does not need to be hidden. Its job is to stop one computation from helping with every account.
The salt belongs beside the derived hash in the record. NIST SP 800-63B says salts must be at least 32 bits, chosen to minimize collisions, and stored with the resulting hash and password-hashing parameters. Modern libraries normally generate longer random salts and encode the algorithm, cost settings, salt, and result in one formatted string.
A salt is not a password, an encryption key, or a secret token. Exposing it does not let someone log in. It does mean that an implementation must use a cryptographically secure random generator and must never reuse a fixed application-wide salt.

Why password storage uses hashing, not encryption
Encryption is designed so an authorized party can recover the original data with a key. Password verification should not require recovery. The server receives a candidate password, runs the same password hashing process with the stored parameters, and compares the result with the stored value.
A good password hashing function is deliberately expensive. Fast general-purpose hashes such as SHA-256 are excellent building blocks for many integrity tasks, but they are poor choices for password storage when used by themselves. A guessing attacker can run huge numbers of fast attempts on specialized hardware. Password hashing functions add a work factor, and memory-hard designs require meaningful RAM as well as CPU time.
The goal is not to make a legitimate login painfully slow. The goal is to make each offline guess costly enough that large-scale guessing becomes impractical, while keeping normal verification within the application's performance budget. The chosen settings need testing on the real production class of server and periodic review as hardware changes.
How to choose and tune a password hashing function
The OWASP Password Storage Cheat Sheet recommends Argon2id for new password storage when the platform supports it. Argon2id exposes memory, iteration, and parallelism parameters, so a team can tune the cost. OWASP lists 19 MiB of memory, two iterations, and one degree of parallelism as one minimum configuration, alongside alternatives that trade more memory for fewer passes.
If Argon2id is not available, OWASP lists scrypt as the next choice, with parameters selected to consume substantial memory. bcrypt remains useful for legacy systems, but it has an input limit of 72 bytes in most implementations and should use a work factor of at least 10, subject to performance testing. PBKDF2 is a practical choice where a FIPS-validated construction is required. It relies on a high iteration count and an approved underlying function.

These are starting points, not a universal magic number. The library's encoded output should preserve the algorithm and cost parameters so a verifier can recognize old settings. When a user logs in successfully, the application can check whether the record uses an outdated cost and rehash the supplied password with the current settings. That gradual upgrade avoids a forced reset for every account.
What the server stores during registration and login
During registration or password change, the application receives the password over an authenticated protected connection. It generates a new random salt, runs the selected password hashing function, and stores the algorithm identifier, cost settings, salt, and derived value. It then discards the plaintext password.
During login, the application looks up the account record and parses those parameters. It feeds the submitted password and the stored salt into the same function. The result is compared using the library's safe verification routine. A mismatch should produce the same general error as an unknown username so the sign-in page does not reveal which accounts exist.
The database never needs the original password. It also should not receive a password through logging, analytics, crash reports, or debugging output. Transport protection still matters because hashing at rest does not protect a password while it travels from a browser to the server. Session security, rate limiting, and multi-factor authentication address different parts of the authentication problem.
When a pepper adds another layer of protection
A pepper is a secret value applied in addition to the per-record salt. Unlike a salt, it is not stored with the password hash. It can be kept in a secrets manager or hardware security module and used in a keyed construction around the password hashing process.
A pepper can add defense in depth if an attacker obtains only the database. It also creates an operational dependency: losing the pepper can make every stored password unverifiable, while rotating it requires a migration plan. OWASP describes pre-hashing and post-hashing pepper strategies and stresses that the pepper must be protected separately. Do not put it in source control, a database column, or the same backup bundle as the hashes.
Peppering does not replace a salt or a slow password hashing function. If the application has already been compromised and the attacker can call the live verifier, a pepper may not stop online guessing. It is a layer for a specific database-compromise scenario.
Password storage and password policy solve different problems
Storage is only half of the design. NIST recommends allowing long memorized secrets, accepting spaces and Unicode where the system can handle them, and checking new passwords against a blocklist of common or compromised choices. Length and uniqueness usually help more than forcing arbitrary mixtures of symbols.
Do not silently truncate input. Different clients may encode Unicode differently, so the application should use a documented normalization process when it accepts Unicode and test the same behavior across platforms. Password hints that are visible before authentication can also leak information and should not be used as a substitute for recovery.
Rate limiting and multi-factor authentication reduce the impact of online guesses. A password manager can create a different secret for each service, which limits damage when another site is breached. None of these controls changes the need for salted, adaptive hashing on the server.
Implementation mistakes that undo the design
A fixed salt copied into configuration gives every account the same salt and defeats the main benefit of per-record salting. A salt generated with a predictable random source can be guessed. Storing a salt in a secret vault wastes the vault and makes recovery harder; salts are meant to be stored with their hashes.
Using a fast digest such as plain SHA-256, MD5, or SHA-1 leaves the database cheap to attack. Encrypting passwords with a key that sits beside the database creates a reversible target and turns key management into the central problem. Rolling your own password format can also lose parameters, mishandle long inputs, or compare values incorrectly.
Finally, do not invent a custom sequence of hashes because it sounds stronger. Use a maintained library's password-hashing API, verify its parameter encoding, and keep a tested path for rehashing legacy records. Security review should include backups, logs, error messages, password reset tokens, and administrative tools, not only the main user table.
Password storage review checklist
A reviewer should be able to answer these questions from the implementation:
- Is every password record using a fresh, cryptographically random salt?
- Are the algorithm and cost parameters stored with the derived value?
- Is the function designed for passwords and tuned for the available memory and CPU?
- Are old records upgraded after successful verification?
- Are salts and hashes protected from accidental logging?
- If a pepper exists, is it outside the database and covered by a recovery plan?
- Does the service block common passwords, avoid silent truncation, and support long passphrases?
- Are login attempts throttled, and is multi-factor authentication available for sensitive accounts?
What salting cannot protect against
Salting is a narrow control with a very useful job. It prevents identical passwords from sharing a digest and stops precomputed tables from being reused across accounts. It does not identify a weak password, prevent phishing, secure a lost session cookie, or make an exposed live server safe.
Secure password storage is a chain: protected transport, a unique salt, an adaptive memory-hard function, careful parameters, controlled secrets, and a recovery process for upgrades. If a database copy leaks, those choices determine how quickly an attacker can turn records into guesses. The best implementation is usually the least clever one: use a well-reviewed library, store the metadata needed for verification, and test the complete path before release.
If a password database is stolen, the incident still requires investigation, resets, and user protection. Proper salting and adaptive hashing do not erase the breach. They slow the conversion of stolen records into usable passwords.
