What Is Salting in Security? Password Hashing Explained

What Is Salting in Security? Password Hashing Explained

In June 2012, an attacker dumped 117 million LinkedIn account records onto a Russian forum. The list was a wall of unsalted SHA-1 hashes. SHA-1 is fast. Without a salt to break up identical passwords, the same hash sat next to the same hash thousands of times. Within roughly seventy-two hours, security researchers had cracked about ninety percent of the file. When the breach resurfaced in May 2016 as part of a 167-million-record dataset, those credentials fed credential-stuffing campaigns for years.

The defense that would have softened that breach into a footnote — to add salt to every stored password — already existed in 1979. It is called salting. It is not new, not expensive, and not particularly clever. It is the difference between a database leak being a contained incident and being everyone's password problem for half a decade.

Most modern password-storage failures still come from the same root cause: somebody decided "we use SHA-256" was good enough. This article walks through what salting actually is, how it works in practice, what it does and does not protect against, and what OWASP recommends as the 2026 default.

What is salting in security and password hashing

Strip the topic down to one sentence. Salting means adding random data to a password before that password gets hashed. The random value — the salt — is generated once per account when the salt is generated alongside the new password hash, stored in the clear next to the resulting hash, and read back on every login. It is not a key. Not a secret. Not encryption. A public modifier with exactly one job: making sure that when two users have the same password, they never share the same stored hashed password.

Hashing on its own is one-way. Push a password through SHA-256 or Argon2id and you get a fixed-length value that no algorithm can reverse. The catch is that "no algorithm" excludes one cheap shortcut — looking the hash up in a precomputed dictionary. Salting closes that shortcut. Adding a salt to the hashing process makes a unique hash for each user, even when their underlying password text is word-for-word identical. Common passwords like "password" and "qwerty" no longer collide in the database. Salting ensures the leak of one row tells the attacker nothing about anyone else.

Three rules separate a usable salt from a useless one. First rule: every password gets its own random salt, freshly generated when the user signs up or resets. A salted password reused across accounts is hardly better than an unsalted password — the bulk attack works the same way. Second rule: the salt may be public. Knowing the salt by itself gives an attacker zero shortcut against the underlying hash function. So the salt lives in the database next to the hash, and that placement is fine, even encouraged. Third rule: the salt values come out of a cryptographically secure pseudorandom number generator. Linux exposes one through `getrandom(2)`. Node calls it `crypto.randomBytes`. Python's standard library wraps it in `secrets.token_bytes`. The non-cryptographic `Math.random()` is unsuitable, even though it appears in real audited code far more often than it should.

Two people picking "hunter2" as their password should walk away with two entirely different stored hashes. Salting is what makes that happen. Skip the salt and the database leaks not just hashes, but the social graph of who shares which password with whom.

How does password salting work

The mechanism breaks into four steps, and ten years of password-storage breaches show that almost every failure happens because somebody skipped one of them.

Step one runs at registration time. The application asks a CSPRNG for sixteen to thirty-two random bytes. That is the salt. OWASP's 2025 guidance treats sixteen bytes (128 bits) as the floor; auth0 and several password-manager vendors recommend going to thirty-two. NIST's SP 800-63B-4 sets a four-byte minimum but warns that birthday-bound collisions start showing up around sixty-five thousand users at that size, which is why nobody actually uses four bytes.

Step two combines the salt with the user's chosen password. Concatenation is the most common form, with the salt placed before or after the password string. Some systems use HMAC instead, treating the salt as the HMAC key, which sidesteps a few obscure length-extension issues with raw concatenation.

Step three runs the combined input through a password-hashing algorithm — what cryptography textbooks call a key derivation function, or KDF. This is the step where most teams used to fall down, often by reaching for a generic hash algorithm and considering the job done. Plain SHA-256 is unsuitable as a way to store passwords on its own. A consumer GPU in 2026 cycles through over one hundred billion SHA-256 hashes per second, so even with a salt the per-guess cost in cryptographic hashing stays trivial. Salting kills rainbow table attack vectors. It does not slow down password cracking by brute force on its own. The right tool here is a deliberately slow, memory-hard function: Argon2id is the OWASP-preferred default, with scrypt and bcrypt acceptable, and PBKDF2 with very high iteration counts allowed where FIPS-140 compliance forces the choice.

Step four stores both the hash and salt. Modern libraries handle the storage format so you do not have to manage the stored salt by hand. A bcrypt output looks like `$2b$12$9f4c8a7b...kQR8YZpL9` — that single string carries the algorithm identifier, the cost factor, the salt, and the hash value. Argon2id uses the analogous PHC string format, `$argon2id$v=19$m=19456,t=2,p=1$$`. You write that string into a database column and walk away. The library has done everything needed to store passwords securely — generated random data for the salt, run the password through a slow hash algorithm, and bundled the result for retrieval.

Login follows the same pipeline in reverse. The application looks up the user's salt and stored hash, runs the submitted password through the same algorithm with the same salt, and compares the result against the stored hash using a constant-time comparison. Constant-time matters: a naive `==` leaks information about how many bytes matched, which has produced real timing-attack vulnerabilities. Use `hmac.compare_digest`, `crypto.timingSafeEqual`, or your framework's equivalent.

Salting in Security

Why salting matters: the rainbow table problem

Rainbow tables are the specific attack salting was invented to defeat. Picture a precomputed lookup table that maps every plausible password — every dictionary word, common variation, leaked-list entry — to its SHA-256 hash. These tables exist, they are sold on cracking forums, and they run into terabytes. Once an attacker holds a database dump of unsalted hashes, looking up a hash in a rainbow table is a database query, not a cracking job. LinkedIn 2012 went exactly this way: 117 million unsalted SHA-1 hashes against a rainbow table, ninety percent cracked in roughly three days.

Add a per-user salt and rainbow tables stop working. The attacker would need a separate precomputed table for each unique salt value. With a sixteen-byte random salt, that means 117 million different tables for 117 million users — and each table would still be terabytes large. Storage costs alone make this infeasible. The attack drops out of the threat model.

That is the whole job of a salt. It is narrow on purpose. Salting does not slow down a determined brute-force attempt against one specific user. Salting does not stop credential-stuffing from a previous leak. Salting does not help if the password itself is "123456" — the attacker tries the obvious dictionary, and the salt gets recomputed for each guess but does not change the per-guess cost meaningfully.

What salting reliably mitigates: rainbow tables, and the leakage of password-reuse information across users in the same database. What slow hashing mitigates: per-guess cost. What pepper mitigates: database-only theft. What MFA mitigates: credential stuffing. Salting is one layer of security in a stack — adding a salt to crack passwords harder is not the same as making each password individually hard to guess. A different hash for every user is the goal salting achieves; making each guess expensive is a separate problem.

Hashing vs encryption vs salting

Three terms that get confused, especially in writeups by non-specialists. They are not interchangeable.

  Encryption Hashing Salting
Reversible Yes, with the key No, by design Modifier, not standalone
Output length Variable, matches input Fixed (typically 256 bits) N/A — alters hash input
Used for Confidentiality of data Integrity, password storage Strengthening hashes
Key required Yes, secret No No, uses a random salt

Encryption protects data you want to retrieve later. The receiver holds the key, applies it, and reads the original. Hashing is one-way: once a password becomes a hash, no algorithm reverses it. Salting is a modifier you apply to the input of a hash function — it has no meaning on its own.

Adobe's 2013 breach is the cautionary example here. Adobe stored 153 million user records by encrypting passwords with 3DES in ECB mode under a single site-wide key. That choice was a triple failure. Encryption is the wrong primitive for password storage. ECB mode leaks identical inputs as identical outputs. Reusing one key across the whole database meant that decoding the key once decoded all 153 million records. Schneier called it one of the worst password breaches in history. The fix at every layer was to switch to salted, slow hashing.

Salt vs pepper: the two-layer defense

Pepper is salt's lesser-known cousin. The concept: an additional secret value, applied to every password the application processes, but stored outside the database. An environment variable, a key-management service entry, or an HSM-resident key. The salt lives next to the hash in plaintext. The pepper does not.

The threat model is database-only theft. If an attacker steals just the database — through SQL injection, a misconfigured backup, or a leaked dump — they have salts and hashes but not the pepper. Without the pepper, they cannot even start brute-forcing because every guess they hash is missing the global secret modifier.

A typical implementation runs `argon2id(salt + password + pepper)` or, more carefully, computes `HMAC(pepper, password + salt)` first and feeds the result into Argon2id. OWASP recommends pepper for high-value systems but acknowledges the tradeoff: pepper rotation is operationally painful. Rotating it means rehashing every user's password on next login, and if the pepper is ever lost without a backup, every account becomes uncheckable. Most consumer applications skip pepper. Banks, government, and healthcare workloads usually do not.

Modern password hashing in 2026

OWASP's 2025 password-storage guidance ranks four algorithms in order of preference. Salt is built into all of them — you do not generate it manually.

Algorithm OWASP 2025 minimum Spec
Argon2id (preferred) m=19 MiB, t=2, p=1 RFC 9106 (2021)
scrypt N=2^17, r=8, p=1 RFC 7914 (2016)
bcrypt (legacy) cost ≥ 10; 72-byte input cap Niels Provos, 1999
PBKDF2-HMAC-SHA256 600,000 iterations RFC 2898; FIPS-only

Argon2id is memory-hard, meaning each guess costs not just CPU cycles but a defined chunk of RAM. The OWASP minimum of 19 MiB per hash means that an attacker building a custom ASIC has to also build a lot of fast memory, and memory is the expensive part of any rig. The "id" variant combines Argon2i (side-channel resistant) and Argon2d (GPU resistant) by running Argon2i for the first half and Argon2d for the second.

scrypt predates Argon2id and works on the same memory-hard principle. bcrypt is older still, simpler, and has a hard 72-byte input limit that occasionally trips up applications using long passphrases — pre-hash with SHA-256 and base64-encode if you need longer inputs. PBKDF2 is the slow-but-FIPS-compliant choice; OWASP raised its iteration count to 600,000 for SHA-256 in 2023, up from 310,000.

What does not belong on this list: plain SHA-256, plain SHA-512, MD5, SHA-1, and any custom function ending in "+ salt." Speed is the disqualifier. SHA-256 was designed to be fast on commodity hardware. That is the opposite of what password storage needs.

Common salting mistakes that show up in real audits

Real-world breach reports keep flagging the same handful of errors.

Reusing one salt for every user defeats the entire mechanism — the rainbow-table problem returns, just with one extra step. Using the username as the salt is worse, because usernames repeat across systems and rainbow tables can be precomputed for popular ones. Salt lengths under sixteen bytes start to admit collisions at the scale of large user bases. Generating salt with a non-cryptographic random function — `Math.random()`, `time(0)` mod something, the language's default RNG — produces predictable values that strip salting of its benefit.

A subtler mistake is storing the salt on a different server "for security." The salt is not a secret. Splitting it across systems adds operational complexity without adding any cryptographic strength. Store it in the same row as the hash. Modern PHC strings do this for you.

The biggest one, in 2026 audits, is hashing with plain SHA-256 plus salt and shipping. The salt prevents rainbow tables. It does nothing about a GPU farm running ten billion guesses per second against a single account. The fix is not adding a second salt; it is switching to a slow KDF like Argon2id.

Last: legacy unsalted hashes. Migration is not optional. The standard pattern is to verify the legacy hash on next login, rehash with the modern algorithm, and overwrite. For users who never log in again, force a password reset on a fixed timeline.

Salting in Security

Why salting alone is not enough

Salting is a layer, not a fortress. It defeats one specific attack — precomputed tables — and reveals nothing about the strength of the underlying password. Brute force still works against weak passwords. Credential stuffing still works against reused passwords. Phishing still works against any password.

Real password security in 2026 stacks four defenses. Argon2id with a sixteen-byte per-user salt handles storage. A pepper, optional but worthwhile for sensitive systems, blocks database-only theft. Multi-factor authentication blocks credential stuffing and most phishing. Continuous breach monitoring — services like Have I Been Pwned and its API — catches the moment a user's credentials show up in a leak so they can be forced to reset.

Skip any one of those layers and an attacker eventually finds the gap. Salting alone, or any one defense alone, is exactly what every breach postmortem in the last decade has identified as the failure point.

Any questions?

No. Usernames repeat across services, attackers precompute rainbow tables for the popular ones, and predictable salts collapse back into the rainbow-table problem salting is meant to kill. Always pull the bytes from a CSPRNG: `secrets.token_bytes(16)` in Python, `crypto.randomBytes(16)` in Node.

OWASP`s 2025 guidance puts the floor at sixteen bytes — 128 bits. NIST technically allows four-byte salts but birthday-bound collisions show up around sixty-five thousand users at that size. With sixteen-byte salts the collision probability lands somewhere near one in 2^64. Practically: never.

Yes, against rainbow-table lookups and against the leak of password-reuse across users. No, against an attacker patiently brute-forcing one specific account. Real protection needs salting paired with a slow, memory-hard hash such as Argon2id, and ideally MFA layered on top.

Salt is the random value mixed into a password right before hashing, the trick that makes every stored hash unique even for shared passwords. Not a secret. Not a key. Not encryption. It lives openly with the hash and gets regenerated for each new account.

A passphrase tip, not a salting rule. It tells users to chain three unrelated words ("trumpet-glacier-velvet") for memory plus entropy. Salting is about how the database stores whatever password you pick. The three-word rule is about choosing the password in the first place. Both layers help.

Salting drops a unique random value into a password before that password gets hashed. The point: two users with the exact same password walk away with two different stored hashes. The salt is per-account, public, and sits in the database right next to the hash. Its real job is killing rainbow tables.

Ready to Get Started?

Create an account and start accepting payments – no contracts or KYC required. Or, contact us to design a custom package for your business.

Make first step

Always know what you pay

Integrated per-transaction pricing with no hidden fees

Start your integration

Set up Plisio swiftly in just 10 minutes.