Rainbow Table
debt(d5/e3/b5/t5)
Closest to 'specialist tool catches it' (d5), semgrep rules can flag md5($password)/sha1($password) patterns without salt, but unsalted hashing won't show up in default linting.
Closest to 'simple parameterised fix' (e3), swapping md5/sha1 to password_hash() is straightforward in code but requires a migration path for existing stored hashes (rehash on next login), which is more than a one-liner.
Closest to 'persistent productivity tax' (b5), password storage is load-bearing across auth flows (registration, login, reset) — picking the wrong scheme shapes every auth touchpoint until migrated.
Closest to 'notable trap' (t5), the misconception that long passwords or a single app-wide salt defeats rainbow tables is a documented gotcha — devs assume hashing alone is safe when per-user salts are the actual defence.
Also Known As
TL;DR
Explanation
Rainbow tables trade disk space for cracking speed. Attackers precompute hashes of millions of common passwords and look up a stolen hash in the table to instantly recover the original password. Salting defeats rainbow tables by ensuring the same password produces a different hash each time — so a rainbow table would need to be computed separately for every possible salt. password_hash() salts automatically. Unsalted MD5/SHA1 hashes of common passwords are crackable in milliseconds.
Common Misconception
Why It Matters
Common Mistakes
- Storing unsalted MD5 or SHA1 password hashes — rainbow tables crack them in milliseconds.
- Using a single application-wide salt — rainbow tables can be precomputed for that specific salt.
- Using salted hashes but fast algorithms (MD5, SHA256) — salts defeat rainbow tables but not GPU brute force; use bcrypt/argon2.
- Not using password_hash() which handles salting automatically with a secure algorithm.
Code Examples
// Unsalted MD5 — rainbow table cracks instantly:
$hash = md5($password); // 'password123' → '482c811da5d5b4bc6d497ffa98491e38'
// This exact hash appears in every rainbow table
// Correct — password_hash() adds unique salt automatically:
$hash = password_hash($password, PASSWORD_ARGON2ID);
// Rainbow tables: precomputed hash → password mappings
// Defeated by per-password salts
// Bad — unsalted hash is rainbow-table vulnerable:
$hash = md5($password); // rainbow table cracks this instantly
$hash = sha1($password); // same problem
// Good — password_hash() automatically generates and stores a unique salt:
$hash = password_hash($password, PASSWORD_ARGON2ID);
// Hash includes: algorithm, cost, salt, and digest — all in one string
// $argon2id$v=19$m=65536,t=4,p=1\$[22-char-salt]\$[43-char-hash]
// Even if the DB is leaked:
// Each password has a unique salt → no precomputed table helps
// Argon2id's memory-hard algorithm → GPU cracking is expensive