Home / Ciberseguridad / Storing a password without storing it: salt, memory, and Argon2

Storing a password without storing it: salt, memory, and Argon2

When you sign up for a service, the server should not store your password. It stores something that looks like gibberish: a cryptographic digest, or hash, of that password. The idea is a one-way process: computing the hash from the password is trivial, but recovering the password from the hash is, in practice, impossible.

The catch is that not every hash works for this. SHA-256 and MD5, so useful elsewhere, are disastrous for passwords because they were designed to be fast. An attacker with modern GPUs can test billions of combinations per second. For something you need to protect for decades, speed is a vulnerability, not a virtue.

The salt: why two accounts with the same password must not look alike

The salt is a random per-user value mixed with the password before hashing. Its job is to break two classic attacks. The first is rainbow tables: precomputed lists mapping millions of passwords to their hashes. Once a salt is mixed in, the precomputed table becomes useless, because it depends on a value that was unknown when the table was built. The second neutralized attack is password reuse: two users with the same password get different hashes, because each carries its own salt. If a database leaks, an attacker cannot tell at a glance which accounts share a key.

bcrypt: deliberate slowness

bcrypt takes salt a step further. It derives from the Blowfish block cipher and includes an adaptive cost: a numeric parameter that, when increased, multiplies the computation time. Verifying a bcrypt hash at cost 12 takes roughly 250 milliseconds on a typical CPU. That is imperceptible to a user logging in, but it makes mass password cracking prohibitive. The resulting hash is serialized into a self-contained format like $2a$12$K7...salt...hash, which encodes the algorithm, cost, salt, and the hash itself. It also has a known limitation: it ignores input beyond the first 72 bytes, so very long passwords must be pre-processed carefully.

PBKDF2, scrypt, and Argon2: the evolution

PBKDF2 applies the HMAC function thousands of times in a chain to slow down computation. It is simple and available on every platform, but its weakness is low memory usage: an adversary can run thousands of threads in parallel on GPUs or ASICs. scrypt introduced memory-hard hashing: it forces a large block of data to be held in RAM during computation, making parallel hardware expensive. In 2015, the Password Hashing Competition crowned Argon2 as the recommended standard. Its argon2id variant combines a memory-hard step with one resistant to cache side-channel attacks. It is tuned with memory cost (in KiB), iterations, and parallelism, so difficulty can be scaled to the available hardware.

Comparing without clues: the timing attack

Even the perfect hash can leak information if the comparison is done wrong. A classic string comparison returns as soon as it finds the first differing byte; by measuring how long the failure takes, an attacker can, after thousands of attempts, reconstruct the value byte by byte. This is a timing side-channel attack. The fix is a constant-time comparison: it always walks every byte and accumulates any difference into an accumulator, so the duration does not depend on where the mismatch is.

The recipe: the server gets it wrong too

A correct password hash involves a unique salt, a slow memory-hard algorithm (argon2id or bcrypt), and constant-time comparison. You can add a pepper: a secret global value stored on the server, outside the database, mixed into the hash, protecting the data if only the user table leaks. It also helps to re-hash users automatically with the new method the next time they log in after an algorithm upgrade. And never, under any circumstance, store a plain password or use reversible encryption: encryption can be undone, a hash cannot, and that asymmetry is exactly what protects your account when everything else fails.