Hashing and password storage
What Is a Cryptographic Salt?
A salt is a unique random value hashed with each password. Why salts exist, how they stop rainbow tables, where they are stored and what they can't do.

Definition
A salt is a random value that a system generates for each password and combines with it before hashing. The system stores the salt next to the resulting hash and uses it again every time it checks that password.
OWASP defines it as "a unique, randomly generated string that is added to each password as part of the hashing process" [1]. "Unique" is the important word: every stored password gets its own salt, including two passwords that happen to be the same.
A salt is part of password hashing, the slow, one-way process sites should use to store passwords. If you are not sure how hashing differs from encryption, start with Hashing vs Encryption.
The problem salts solve
Same password, same hash
A hash function is deterministic: the same input always gives the same output. Without a salt, every user whose password is sunflower-42 has exactly the same stored hash. An attacker who steals the database can see at once which accounts share a password, and cracking one of them cracks all of them.
Precomputation and rainbow tables
Determinism also allows precomputation. Without salts, an attacker can hash millions of likely passwords once, store the results, and then look up any stolen hash in that list. The expensive work is done before the breach and reused against every site that used the same unsalted hash function.
A plain list of every hash would be huge, so researchers developed ways to trade storage for computation. In 2003 Philippe Oechslin published Making a Faster Cryptanalytic Time-Memory Trade-Off, which introduced what became known as rainbow tables [2]. In simple terms, a rainbow table stores only the start and end of long chains of hash computations. To look up a hash, the attacker recomputes part of a chain instead of storing every entry. The result is a much smaller table that still finds many passwords quickly.
Salts break this approach. A table built for unsalted hashes is useless when each password is combined with its own random salt, because the attacker would need a separate table for every possible salt value. With 128-bit salts, that is not feasible. OWASP summarises the effect: with salts, "an attacker has to crack hashes one at a time using the respective salt" [1]. How Passwords Get Cracked describes what attackers do instead.
Example: two users, one password
This is an illustrative example. We ran the following on Node.js v22.17.0: two calls of Node's built-in crypto.scrypt for the same password, sunflower-42, each with a new 16-byte random salt from crypto.randomBytes, using N=2¹⁷, r=8, p=1 and a 32-byte output. Each line shows the scheme, parameters, salt and hash, Base64-encoded:
User A: scrypt$131072$8$1$KEWjXCEJVLnhhJAWydRIdg==$YepR2XO6oMWEha0lvb6+b0k+HRRkj+oxV9wCM07qqtY=
User B: scrypt$131072$8$1$t++rcs0jDDTlvx4d/HR1ag==$KJWDRwPGogDpTfGUz041VgQdP1JjSVIBnU+GuoPnqkI=The password is identical, but the salts differ, so the hashes differ completely. Someone reading the database cannot tell that the two users chose the same password. If you run the same code, you will get different values again, because the salts are random. (The scrypt$… layout is our own simple format, used in the code example in How Passwords Should Be Stored; it is not a standard.)
To check a login, the server reads the salt from the stored line, hashes the submitted password with that salt and the same parameters, and compares the result with the stored hash.
The diagram sets these salted values against what the same password would look like without a salt:

Where the salt is stored
The salt is stored with the hash, usually in the same field. NIST SP 800-63B Revision 4 requires that "both the salt value and the resulting hash SHALL be stored for each password" [3]. Modern libraries do this for you; OWASP notes that most implementations "automatically generate and manage salts internally" [1].
A standard way to pack all of this into one field is the PHC string format: $<id>[$v=<version>][$<param>=<value>,…][$<salt>[$<hash>]], with salt and hash in Base64 without padding [4]. A string for Argon2id at one of OWASP's minimum settings looks like this:
$argon2id$v=19$m=19456,t=2,p=1$<salt>$<hash>Read left to right: the algorithm (argon2id), the Argon2 version (v=19), the parameters (memory in KiB, iterations, parallelism), then the salt, then the hash. The PHC specification gives a complete example: $argon2id$v=19$m=65536,t=2,p=1$gZiV/M1gPc22ElAH/Jh1Hw$CWOrkoo7oJBQ/iyh7uJ0LO2aLEfrHwTWllSAxT0zRno [4]. Because everything needed to verify the password is in one string, the system can later raise the parameters for new hashes without breaking old ones.
Salt vs pepper
A pepper is a second, optional ingredient. It is also mixed into the hashing process, but it is a secret shared by all passwords and kept outside the database [1]. NIST describes the same idea as an extra keyed hashing step with a secret key that "SHALL be stored separately from the hashed passwords" [3].
| Salt | Pepper | |
|---|---|---|
| Unique per password? | Yes | No, shared by all (or many) passwords |
| Secret? | No | Yes |
| Stored where? | With the hash, in the database | Separately: secrets vault or HSM [1] |
| Main job | Make identical passwords hash differently; defeat precomputed tables | Make stolen hashes untestable without the secret |
| If it leaks | Nothing changes; it was never secret | The extra protection is lost; pepper must be changed [1] |
| Required? | Yes (NIST: SHALL) [3] | Recommended (NIST: SHOULD) [3] |
Design details for peppers, including rotation, are in How Passwords Should Be Stored.
Common misconceptions
"The salt must be kept secret." It does not need to be. The salt sits next to the hash, and the system must read it to verify a login. Its value comes from being unique and unpredictable before the breach, not from being hidden. The secret ingredient, if you want one, is the pepper.
"A salt makes a weak password strong." It does not. If a user chooses password123, the attacker simply hashes password123 with that user's salt, and it is found early in any dictionary attack. Salts stop shortcuts across many accounts; they do nothing for a password that is easy to guess. Strong passwords, such as long random ones from a password generator, are still needed.
"A salt slows down each guess." Not for a single hash. Testing one guess against one salted hash costs about the same as without the salt. What slows each guess is the work factor of the password hashing function. Salts remove the attacker's ability to reuse work across many hashes.
"One salt for the whole site is fine." A single shared salt defeats ready-made rainbow tables, but users with the same password still get the same hash, and an attacker can build one table for your site. OWASP and RFC 9106 both call for a unique salt per password [1][5].
"Any short random value will do." Short salts repeat. If two users share a salt, they lose the benefit for each other. See the next section.
How long should a salt be?
NIST requires a salt of at least 32 bits, "chosen to minimize salt value collisions among stored hashes" [3]. RFC 9106 recommends 16 bytes (128 bits) for password hashing and says 128 bits "is sufficient for all applications" [5]. In practice, let a well-maintained password hashing library generate the salt rather than writing your own.
Frequently asked questions
Is a salt the same as a password hash?
No. The salt is an input to the password hashing function; the hash is the output. Both are stored together [3].
Do I have to generate salts myself?
Usually not. OWASP notes that widely used libraries generate and manage salts automatically [1]. If you do generate one, use a cryptographically secure random generator and make it unique per password.
Can an attacker use the salt if they steal the database?
Yes, and the design assumes they will. The salt lets them test guesses against that one hash, but they must repeat the work for every user, and precomputed tables do not help [1].
Does salting replace a slow hash function?
No. A salted SHA-256 hash is still fast to guess against. You need both: a unique salt and a deliberately slow password hashing function such as Argon2id. See How Passwords Should Be Stored.
What is the difference between a salt and a nonce?
Both are values that should not repeat. RFC 9106 even names Argon2's salt input "Nonce S" and describes it as the salt for password hashing applications [5]. In password storage, "salt" is the usual term.
References
- OWASP, Password Storage Cheat Sheet, accessed 6 October 2026, https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html — definition of a salt; hashes cracked one at a time; libraries manage salts automatically; pepper is shared, secret, stored separately (vault/HSM) and must be changed if compromised.
- P. Oechslin, "Making a Faster Cryptanalytic Time-Memory Trade-Off", Advances in Cryptology — CRYPTO 2003, Lecture Notes in Computer Science, Springer, 2003, https://doi.org/10.1007/978-3-540-45146-4_36 — the paper that introduced rainbow tables (bibliographic record verified via Crossref; full text not opened).
- NIST, SP 800-63B-4: Digital Identity Guidelines — Authentication and Authenticator Management, Revision 4, August 2025, https://pages.nist.gov/800-63-4/sp800-63b.html — sec. 3.1.1.2: salted and hashed; salt ≥ 32 bits chosen to minimise collisions; salt and hash both stored; keyed hash with secret key stored separately.
- C2SP, PHC String Format, accessed 6 October 2026, https://github.com/C2SP/C2SP/blob/main/phc-strings.md — string syntax, unpadded Base64, and the example Argon2id string quoted.
- A. Biryukov, D. Dinu, D. Khovratovich, S. Josefsson, RFC 9106: Argon2 Memory-Hard Function for Password Hashing and Proof-of-Work Applications, September 2021, https://www.rfc-editor.org/rfc/rfc9106 — "Nonce S" is the salt; 16 bytes recommended; SHOULD be unique per password; 128 bits sufficient for all applications.



