Skip to content

What salts actually do (and what they do not)

Salts kill rainbow tables and shared-hash leaks. They do not slow a single targeted crack. Why salted MD5 is still weak, and why you need a slow KDF too.

Published on 5 min read

Salts are one of the most misunderstood pieces of password storage. People know they should add one, they know it makes things "more secure," and then they describe what it does in a way that is just wrong. The usual claim is that salting slows down cracking. It does not. Understanding what a salt actually defeats, and what it leaves wide open, is the difference between a defense that works and a checkbox that feels safe.

Precomputation is the problem salts solve

A hash function is one-way, but it is also deterministic: the same input always produces the same output. That determinism invites a shortcut. Instead of guessing passwords against a target at attack time, you do all the hashing in advance and store the password-to-hash mapping. Then cracking is a lookup, not a computation.

A rainbow table is the space-efficient version of that idea, trading some recomputation for drastically less storage so you can cover billions of candidates in a manageable file. Against an unsalted fast hash like MD5, this is devastating. Someone has already hashed every common password and most short ones. Hand them an unsalted MD5 and the recovery is instant, because the work happened years ago on someone else's hardware.

There is a second problem with unsalted hashes, same root cause. Identical passwords produce identical hashes. Crack one account and you have cracked everyone in the dump who chose the same password, for free, just by grouping matching hash values.

What a salt is and what it changes

A salt is a unique random value generated per password and mixed in before hashing. Store the salt alongside the hash; it is not secret. The point is uniqueness, not concealment.

That single change breaks precomputation cold. A rainbow table built for bare passwords is useless once every hash is salted, because the attacker would need a separate table for every possible salt value, which defeats the entire economy of precomputing. The shared-hash problem dies too: two users with the same password now have different salts and therefore different hashes, so cracking one tells you nothing about the other. Per-user salts force the attacker to do per-target work. That is the whole contribution, and it is genuinely important.

The thing salts do not do

Here is where the misconception bites. A salt does not slow down a single targeted crack at all.

Picture an attacker who wants one specific account. They read the salt straight from the dump (it is sitting right there), prepend it to each candidate, and hash. The salt costs them nothing. Against a salted MD5, the GPU still runs through billions of guesses per second on that one account, and a weak password falls in seconds. The salt stopped the attacker from using a precomputed table and from cracking everyone at once. It did not make the per-target attack any slower, because the speed of the algorithm is the thing that matters for a targeted crack, and salting does not touch the algorithm's speed.

This is why "salted MD5" is not the reassurance people think it is. The salt is doing real work against precomputation and shared hashes. It is doing exactly nothing against a GPU pointed at one weak password.

Slowness comes from iterations, not salt

If salt provides uniqueness, what provides cost? Iterations. A work factor. A password hashing function like bcrypt, sha512crypt or Argon2 deliberately runs the underlying operation thousands of times, so a single guess that took microseconds against MD5 now takes milliseconds. That collapses an attacker's guess rate from billions per second to thousands, and it is the only thing that makes a targeted crack genuinely expensive. The work factor is tunable, so as hardware gets faster you raise it and keep the cost where you want it.

So the two properties are orthogonal. Salt gives uniqueness, defeating precomputation and shared-hash leaks. The work factor gives slowness, defeating fast targeted guessing. You need both, and one cannot stand in for the other. A modern password hash bundles them: it generates a per-hash salt automatically and applies a configurable iteration count, which is why you store passwords with bcrypt or Argon2 rather than rolling salted-MD5 by hand.

Pepper is a different thing

A pepper sometimes gets mixed into this conversation and it should not be confused with salt. A pepper is a secret value, the same for all users, kept outside the database (in application config, an HSM, a secrets manager). The salt lives next to the hash and is not secret; the pepper lives apart and is. The idea is that an attacker who steals only the database, without the application secret, cannot crack offline at all, because they are missing an input. Useful as defense in depth, but it is a supplement, not a substitute. It does not replace a unique salt and it does not replace a slow KDF.

The clean mental model: salt for uniqueness, work factor for cost, pepper as an optional secret on top. Get the first two right with a real KDF and you have defended against both precomputation and brute force. Confuse them, ship salted MD5, and you have stopped the rainbow tables while leaving the front door open to a GPU.