cipher.ink Write a message

Learn · Practical guides

What a password salt is, and why every password needs its own

A salt is a random value that makes identical passwords hash differently and defeats lookup tables. A pepper is a secret kept outside the database. Worked with real hashes.

A salt is a random value, different for every password, that gets mixed into the password before hashing and is stored in the open next to the result. It gives two people with the same password different hashes, and it ruins precomputed attack tables, so an attacker has to crack each stored password separately. A pepper is something else. It's one secret value, kept outside the database, that also gets mixed in. Together they cover what a stolen database gives away. The demo below shows a salt at work, with a hash you can compute yourself.

The problem a salt solves

Sites don't store passwords as typed. They store a hash of each one, so a stolen database doesn't hand over the passwords. But a hash is a function, and the same input always gives the same output. The SHA-256 of password is 5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8 in every database in the world. That has two consequences for an attacker who steals an unsalted table:

  1. Identical passwords are visible. If a thousand users chose password, the table holds a thousand identical hashes. Crack one and you've cracked all of them, and you can see which users share a password without cracking anything.
  2. Work can be done in advance. The attacker can hash a long list of likely passwords once, store the results as a "lookup table", and then recognize any hash in it instantly. A clever compression of such a table, the rainbow table, was described by Philippe Oechslin in 2003 (building on Martin Hellman's 1980 time-memory trade-off). It trades a large amount of storage for a small amount of computation. Against unsalted hashes, one table works against every site.

The 2012 breach of LinkedIn is the standard example. About 6.5 million password hashes were posted online. They were unsalted SHA-1, and cracking tools recovered a large share within days of the leak. Salted, slow hashes would have made the same leak a much harder job.

What a salt does

Before hashing, the site generates a fresh random salt for each password and hashes salt and password together. It stores the salt with the hash, and the salt doesn't need to be secret. At login, the site reads the salt, repeats the calculation and compares. You can see it with ordinary hash tools. These values come from scripts/examples/salt-demo.mjs, which uses SHA-256 for clarity (a real system would use a slow function, see below):

What is hashedSHA-256
password5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8
7f3a9c1e5b20d84apassword19867c78a0778987267d66c51e2876ca44ce0c090c304d30f04693390445346c
2d90b6ee41c7a358passwordf02863dd0d20e0db6adcc23a6393475f610bcd4053d58b9cd9a10bc11c252f0d

Same password, three unrelated hashes. Try it yourself. Type password into the hash generator below, then 7f3a9c1e5b20d84apassword, and compare with the table (pick SHA-256).

Or drop a file here, or click to choose one. Files of any size work.
Algorithms

Nothing you type or drop here leaves your browser. Files are read in 4 MB pieces by a background worker and never uploaded.

The same script measured the effect. With 1,000 users who all chose password, an unsalted table holds one distinct value and a salted one holds 1,000. A precomputed table is useless, because it would need rebuilding for each salt, and a 16-byte salt has 2128, about 3.4 × 1038, possible values. The attacker has to attack each stored password on its own. A salt does not make a weak password strong, and a single user's password still falls on the first guess of a dictionary attack. A salt removes the shortcuts and leaves the guessing.

What a good salt looks like

  • Random, from a secure generator. Not a counter, a user name or a timestamp. A predictable salt lets an attacker prepare tables in advance (random numbers in the browser).
  • Unique per password, including when the user changes it. One salt shared by the whole site (a "global salt") stops a generic public table, but identical passwords still show, and an attacker can build one table for your site.
  • Long enough. The old Unix crypt scheme of the 1970s, described by Robert Morris and Ken Thompson in 1979, used a 12-bit salt (4,096 values). That suited 1970s computers and is far too small now. Modern guidance is at least 16 bytes, and cipher.ink uses a random -byte salt for every passphrase message.
  • Stored openly. It isn't a secret and doesn't need to be. Hiding it adds nothing, since an attacker who has the hashes has the salts.

A salt, a slow hash, and what cipher.ink does

A salt alone doesn't make a stored password safe. A graphics card can try a fast hash such as MD5 or SHA-256 ten billion or more times a second, so a salted fast hash still falls to a determined attacker against weak passwords (how long a cracking run takes). The other half of the job is a slow, memory-hungry function, meaning bcrypt, scrypt or Argon2id, or PBKDF2 with a high iteration count. All of them take a salt as an input and handle the generating and storing for you. In cipher.ink the passphrase goes through PBKDF2-HMAC-SHA256 with rounds and the random salt to make the key that encrypts the message (PBKDF2, password hashing). The salt travels in the open next to the ciphertext, and that's exactly what makes it work, because the recipient's browser needs it to get the same key.

What a pepper is, and when it helps

A pepper is a secret value added to every password before hashing. Think of a salt that's the same for everyone and is not stored in the database. It lives somewhere else, such as the application's configuration, a secrets manager or a hardware security module. Often it's applied as an HMAC. HMAC-SHA-256(pepper, password) is computed first, and the result goes to the slow hash (the demo script prints one such value, for a made-up key). The benefit is specific. If an attacker steals only the database, through an SQL injection for instance, they hold hashes they can't even start to attack without the pepper. If they get the application server's secrets too, the pepper has bought nothing.

It has costs. A lost pepper makes every password unusable, rotating one means rehashing or keeping several versions, and it does nothing if the application itself is breached. OWASP's password storage guidance treats it as an optional extra on top of a proper salted, slow hash, not a replacement. For most sites the first priority is a modern password hashing function with its automatic salt, and the pepper is a refinement for teams who can look after a secret well.

Common questions

Is the salt secret?

No. It's stored beside the hash and is public in any database leak. Its job is to be unique and random, not hidden.

Can I just hash the password twice instead?

No. Hashing twice doesn't stop identical passwords producing identical hashes, and it does nothing against tables. Use a purpose-built function (Argon2id, scrypt, bcrypt, PBKDF2) that includes a salt and a work factor.

Does a salt protect weak passwords?

Not from being guessed. It stops mass attacks and shared work, so each account has to be attacked on its own. A weak password on its own is still found quickly, which is why strength still matters (a strong passphrase).

Do I need to do this myself?

Almost never. Use your language's password hashing library or framework function. It generates the salt, sets the work factor and stores both in one string, and writing your own is how mistakes happen.

Further reading

  1. OWASP Password Storage Cheat Sheet (salting and peppering)
  2. Morris and Thompson: Password Security: A Case History (1979)
  3. Oechslin: Making a Faster Cryptanalytic Time-Memory Trade-Off (2003)
  4. RFC 9106: Argon2, the Memory-Hard Function for Password Hashing
  5. NIST SP 800-132: Recommendation for Password-Based Key Derivation