Weak Password Hash
debt(d5/e3/b3/t7)
Closest to 'specialist tool catches it' (d5). The detection_hints list semgrep, psalm, and phpstan as tools that can catch the code_pattern (md5($password), sha1($password), etc.). These are specialist SAST/static-analysis tools, not the default compiler or a standard linter, so the issue is not caught instantly but is detectable with deliberate tooling.
Closest to 'simple parameterised fix' (e3). The quick_fix is a direct replacement: swap md5()/sha1()/sha256()/crypt() with password_hash($pass, PASSWORD_ARGON2ID). However, common_mistakes also note needing to add password_needs_rehash() calls on login for upgrading existing hashes, which adds a small but localised refactor beyond a pure one-liner — slightly above e1 but contained within authentication code.
Closest to 'localised tax' (b3). The misuse is typically confined to authentication/password-handling code (a specific component), not spread across the entire codebase. While it's a security-critical choice, it doesn't impose a persistent tax on unrelated work streams — the blast radius is limited to login, registration, and password-reset flows.
Closest to 'serious trap' (t7). The misconception field explicitly states: 'Adding a salt to MD5 makes it secure' — this directly contradicts how developers reason about cryptographic primitives (salting is taught as the fix for rainbow tables, so it feels sufficient). The trap contradicts established mental models from general cryptography education, leading competent developers who don't specialise in password hashing to guess wrong about the security of salted MD5/SHA1.
Also Known As
TL;DR
Explanation
Password hashing requires algorithms designed to be slow: bcrypt, scrypt, or Argon2id. MD5 can hash 10 billion passwords per second on a modern GPU; bcrypt with cost 12 takes ~300ms. A leaked database with MD5-hashed passwords is cracked within hours. PHP's password_hash() with PASSWORD_BCRYPT or PASSWORD_ARGON2ID handles salting, iteration count, and future algorithm upgrades automatically. Never implement password hashing manually.
Common Misconception
Why It Matters
Common Mistakes
- md5($password) or sha1($password) — never use these for passwords.
- sha256($salt . $password) — still fast; use password_hash() instead.
- Not using password_needs_rehash() — existing bcrypt hashes should be upgraded to Argon2id on next login.
- Setting bcrypt cost too low — cost 10 is the old default; use 12+ on modern hardware.
Avoid When
- Never use md5(), sha1(), sha256(), or any general-purpose hash for passwords — they are too fast.
- Never add a static salt manually — password_hash() generates a cryptographically secure random salt automatically.
When To Use
- Use password_hash() with PASSWORD_ARGON2ID for all new password storage — it is the current best practice.
- Call password_needs_rehash() on login to transparently upgrade users from older algorithms.
Code Examples
// Trivially cracked:
$hash = md5($password);
$hash = sha1($password);
$hash = hash('sha256', $salt . $password); // Still fast
// Verification:
if (md5($input) === $stored) { /* vulnerable */ }
// Secure — slow by design:
$hash = password_hash($password, PASSWORD_ARGON2ID, [
'memory_cost' => 65536,
'time_cost' => 4,
'threads' => 2,
]);
// Verify:
if (password_verify($input, $stored)) {
// Upgrade hash if algorithm changed:
if (password_needs_rehash($stored, PASSWORD_ARGON2ID)) {
$newHash = password_hash($input, PASSWORD_ARGON2ID);
updateHash($userId, $newHash);
}
}