Introduction

Every website you log into stores something in a database — but if they are doing their job, it is not your password. It is a hash: the output of a one-way mathematical function. When you log in, the site hashes what you typed and compares it to the stored value. Your real password never sits on disk.

The naive approach is to use a fast cryptographic hash like MD5 or SHA-256. Fast sounds good, right? In 2009 the website RockYou was breached and 32 million passwords were stolen — stored in plain text. Sites that used fast hashes fared little better. Within days, attackers with commodity GPUs had cracked most of them by hashing billions of guesses per second until they matched.

The fundamental insight of modern password hashing is: make the hash function deliberately, tuneably slow. Not slow for the user logging in — one hash taking 100 ms is imperceptible. But slow enough that checking a billion guesses takes not hours but centuries.

Three algorithms define the field. bcrypt (Niels Provos and David Mazières, 1999) introduced a work factor that exponentially scales cost. scrypt (Colin Percival, 2009) added memory-hardness so that cheap custom hardware loses its advantage. Argon2 (Alex Biryukov, Daniel Dinu, and Dmitry Khovratovich, 2015) — winner of the Password Hashing Competition — lets you tune time, memory, and parallelism independently. All three are deliberately polynomial-in-the-work-factor slow, and that is not a bug. It is the entire security guarantee.

The Work Factor in Action

The slider below controls a bcrypt-style work factor (called cost in bcrypt). Each increment doubles the number of internal rounds. Observe how a modest change in the slider explodes the time an attacker would need to check every possible short password.

<div class="demo">
  <div class="controls">
    <label for="cost">{{label_cost}} <strong id="cost-val">10</strong></label>
    <input type="range" id="cost" min="4" max="20" value="10" step="1">
  </div>
  <div class="controls">
    <label for="gpu">{{label_gpu}} <strong id="gpu-val">10,000</strong> {{label_hps}}</label>
    <input type="range" id="gpu" min="1" max="6" value="4" step="1">
  </div>
  <div class="result-box" id="result">
    <div class="row"><span class="label">{{row_rounds}}</span><span class="value" id="rounds"></span></div>
    <div class="row"><span class="label">{{row_speed}}</span><span class="value" id="speed"></span></div>
    <div class="row"><span class="label">{{row_space}}</span><span class="value">208,827,064,576</span></div>
    <div class="row highlight"><span class="label">{{row_time}}</span><span class="value" id="time"></span></div>
    <div class="bar-wrap"><div id="bar" class="bar"></div></div>
  </div>
  <div class="note" id="note"></div>
</div>
* { box-sizing: border-box; }
body { font-family: system-ui, sans-serif; margin: 0; color: #222; background: #f7f8fa; }
.demo { max-width: 520px; margin: 0 auto; padding: 1rem; }
.controls { margin-bottom: 1rem; }
.controls label { display: block; font-size: .9rem; margin-bottom: .3rem; color: #555; }
.controls input[type=range] { width: 100%; accent-color: #1d3557; }
.result-box { background: #fff; border: 1px solid #dde3ea; border-radius: 10px; padding: 1rem; margin-top: .5rem; }
.row { display: flex; justify-content: space-between; align-items: baseline; padding: .3rem 0; border-bottom: 1px solid #f0f0f0; font-size: .88rem; }
.row:last-of-type { border-bottom: none; }
.label { color: #555; }
.value { font-family: ui-monospace, monospace; font-weight: 700; color: #1d3557; }
.row.highlight .label { color: #1d3557; font-weight: 700; }
.row.highlight .value { color: #0a7d33; font-size: 1rem; }
.bar-wrap { background: #e8eef3; border-radius: 6px; height: 12px; margin-top: .8rem; overflow: hidden; }
.bar { height: 100%; background: linear-gradient(90deg, #0a7d33, #e63946); border-radius: 6px; transition: width .4s; }
.note { margin-top: .7rem; font-size: .83rem; color: #444; line-height: 1.45; min-height: 2.5em; }
// Code not found

Notice the asymmetry: your login performs exactly one hash — the cost is imperceptible. An attacker checking millions of candidates must pay that cost every single time. Raising the work factor by 4 steps multiplies the attacker's time by 16 while adding only ~100 ms to your login. That is the power of tunable hardness.

The Real Complexity

Password hashing is a solved engineering problem — we know exactly what to do — but the solution rests on subtle computational ideas.

  • One-wayness. A cryptographic hash is easy to compute forward but believed to be hard to invert. This is a conjecture, not a theorem. If P vs NP were resolved in the wrong direction, one-way functions might not exist, and the whole edifice would collapse. In practice, no one has broken SHA-3 or the bcrypt core.
  • Work factors make brute force polynomial-to-exponential. bcrypt's cost parameter C makes each hash attempt take O(2C)O(2^{C}) rounds. An attacker who can check 10910^{9} MD5 hashes per second can check only ~10310^{3} bcrypt hashes at cost 20. The same hardware, a factor of a million harder.
  • Memory-hardness closes the hardware gap. A GPU has thousands of tiny cores but limited memory per core. scrypt and Argon2 require gigabytes of memory during hashing, so a GPU with 10 000 cores still gets at most a handful of parallel hashes — nearly the same as a CPU. Custom ASICs (the bane of Bitcoin) become economically useless.
  • Parallelism control. Argon2 lets you specify a lane count, forcing the hash to occupy p parallel threads. Even machines with exotic architectures cannot do better than serial at the individual-hash level.
  • Status: solved but evolving. bcrypt, scrypt, and Argon2 are the current standards. SHA-256 and MD5 for passwords are categorically wrong — not because those hashes are broken for their intended use, but because they are too fast. The attack surface is brute force, not cryptanalysis.

For a deeper look at why search problems resist shortcuts, see the article on factoring — the hardness of breaking RSA relies on analogous asymmetry between easy and hard directions.

Where It Matters

The "deliberately slow hash" principle shows up everywhere credentials or keys need protecting:

  • User authentication. Every modern web framework (Django, Rails, Laravel, Spring Security) defaults to bcrypt or Argon2 for password_hash. Stored password databases become useless to attackers even after a breach.
  • Disk and file encryption. When you type a passphrase to unlock a laptop or a VeraCrypt volume, a key-derivation function (KDF) — often PBKDF2, scrypt, or Argon2 — stretches that passphrase into a 256-bit encryption key. The slow hash is what makes "just guess every passphrase" infeasible.
  • Cryptocurrency wallets. HD wallets derive private keys from a seed phrase using PBKDF2-HMAC-SHA512 with 2048 rounds (BIP-39). The slowness prevents offline attacks on stolen seed backups.
  • Password managers. Apps like Bitwarden and 1Password use Argon2 to derive the master key from your master password. Even if the vault file leaks, cracking it is intended to take longer than the universe has existed for strong passwords.
  • Hardware security tokens. Devices like YubiKey derive challenge-response keys using slow KDFs to limit offline PIN cracking even if the firmware is extracted.

The common thread: anywhere a human-memorable secret must protect machine-scale data, a slow hash stands in the gap. See also how discrete logarithms underpin the key-exchange protocols that deliver those passwords over the network in the first place.

Conclusion

The insight behind password hashing is deceptively simple: one slow hash does not hurt you; a billion slow hashes crush an attacker. By making the work factor tunable, bcrypt, scrypt, and Argon2 let defenders keep pace with faster hardware simply by incrementing a number.

MD5 and SHA-256 are magnificent tools — for integrity checks, digital signatures, and message authentication. For storing passwords, they are catastrophically wrong. The RockYou breach, the LinkedIn breach, the Adobe breach — nearly every major credential leak in history has one thing in common: passwords protected only by a fast hash, or nothing at all.

The good news is that the correct solution is known, standardized, and freely available. Raise the work factor whenever hardware gets faster. Prefer memory-hard algorithms. Never store passwords in plain text or with a fast hash. These three rules, applied consistently, make a stolen database nearly worthless — and that is the closest thing to a solved problem that applied cryptography has to offer.

Share this article

Pick a channel — or use your device's native share sheet.

Comments

Loading comments...

https://www.kipuhub.com/en/article/password-hashing/Content licensed under CC BY-NC 4.0.