Password Hashing: What NOT to Do
Critical engineering failures in credential storage, GPU cracking mechanics, and the modern Argon2id upgrade standard.
Password hashing requires slow, memory-hard key derivation functions specifically engineered to defeat mass-parallel GPU and ASIC brute-forcing. The most devastating password storage mistakes in production are using fast message-digest hashes (like SHA-256 or MD5), reusing global or static salts, under-tuning work cost factors, and performing comparisons with early-terminating equality operators.
Passwords require slow, memory-hard functions like Argon2id (m = 65,536 KiB, t = 3, p = 4) or bcrypt (cost = 12) with cryptographically random per-user salts. Using general-purpose algorithms like SHA-256 allows an attacker with a single modern GPU to attempt over 8 billion guesses every second, turning strong user passwords into cracked credentials in hours.
01. The Plain-Language Analogy
The Free-Spinning Turnstile vs. The 30-Second Hydraulic Airlock
Imagine an intruder attempting to break into a secure building by randomly trying millions of fake master keys. If the building installs a lightweight, frictionless revolving turnstile (analogous to SHA-256), the intruder can spin it 8 billion times per second without resistance, testing millions of combinations in the blink of an eye.
A memory-hard password function like Argon2id is a heavy steel submarine airlock door that requires 250 milliseconds of high-pressure hydraulic pumping and 64 megabytes of reservoir fluid to cycle open once. To an authorized user logging in once an hour, waiting a quarter of a second is completely imperceptible. But to an attacker attempting to test 100 million stolen combinations, the hydraulic door demands billions of dollars of electricity and memory hardware, making brute-force mathematically impossible.
02. Step-by-Step Mechanics of 5 Lethal Mistakes
The Vulnerability: SHA-256 executes in pure register arithmetic requiring zero external RAM. A single NVIDIA RTX 4090 GPU contains 16,384 CUDA cores that compute over 8.5 billion SHA-256 hashes per second. An 8-character alphanumeric password has 62^8 (~2.18 x 10^14) combinations, cracked by an 8-GPU rig in under 55 minutes.
The Vulnerability: Without unique per-user salts, every user who selects "Summer2026!" shares the exact same digest in the database. Attackers download precomputed rainbow tables or crack the password once to compromise dozens of accounts simultaneously.
The Vulnerability: Setting bcrypt cost to 4 or 6 makes the hashing cycle complete in less than 1 millisecond. In accordance with Moore's law, hardware becomes twice as fast every few years; an uncalibrated cost factor leaves legacy databases vulnerable.
The Vulnerability: Storing only raw hex bytes in a database table without an algorithm prefix or parameter metadata prevents the application from upgrading security parameters as computational power increases.
$argon2id$v=19$m=65536,t=3,p=4$...). On every successful login, check if the hash parameters match current policy and seamlessly re-hash if necessary.
The Vulnerability: If the client browser hashes the password with SHA-256 and transmits that hash to the server, that hash becomes the effective plaintext password. Anyone intercepting the hash can replay it directly to authenticate without ever needing to invert it.
03. Worked Example: GPU Cracking Speeds & Re-Hashing on Login
Examine empirical throughput on an NVIDIA RTX 4090 GPU and the production pattern for migrating legacy hashes:
Argon2id reduces brute-force efficiency by over 700,000,000x compared to SHA-256, neutralizing GPU clusters.
import argon2 from 'argon2';
const TARGET_OPTIONS = {
type: argon2.argon2id,
memoryCost: 65536, // 64 MiB
timeCost: 3, // 3 iterations
parallelism: 4
};
export async function loginAndUpgrade(user, candidatePassword) {
// 1. Verify candidate password against stored Modular Crypt Format hash
const isValid = await argon2.verify(user.passwordHash, candidatePassword);
if (!isValid) return false;
// 2. Check if the stored hash meets modern security parameters
if (argon2.needsRehash(user.passwordHash, TARGET_OPTIONS)) {
// 3. Compute upgraded hash using new parameters and persist
const newHash = await argon2.hash(candidatePassword, TARGET_OPTIONS);
await db.users.update({ id: user.id }, { passwordHash: newHash });
}
return true;
} This pattern allows legacy user databases to transparently upgrade security parameters without forcing mass password resets.
04. Common Misconceptions Corrected
Output bit length protects against birthday collision attacks on certificates and transactions. It does nothing to slow down an attacker guessing passwords. SHA-512 computes on GPUs at over 2.5 billion guesses per second; it has zero memory hardness and zero configurable cost factor.
Salting only stops precomputed rainbow tables and prevents two users with "Password123" from sharing the same hash. If the user chooses a 6-character common word, an attacker testing dictionary words against a fast hash will crack the salted password in under a second. Memory hardness and computational delay are required to stop dictionary attacks.
A naive for loop executing SHA-256 repeatedly stays completely inside the GPU's ultra-fast internal registers. True key derivation functions (like PBKDF2 with HMAC, scrypt, or Argon2) enforce strict pseudo-random expansions or multi-megabyte matrix lookups that force external memory access.
05. Password Storage Standard Matrix (OWASP & NIST SP 800-63B)
| Algorithm | Memory Hard? | Recommended Parameters | OWASP / NIST Status |
|---|---|---|---|
| Argon2id (RFC 9106) | Yes (64 MiB+) | m = 65,536 KiB, t = 3, p = 4 | Top Recommendation (Winner of PHC) |
| bcrypt | Moderate (4 KiB) | Cost factor ≥ 12 | Approved (Universal library support) |
| scrypt (RFC 7914) | Yes (32 MiB+) | N = 65,536, r = 8, p = 1 | Approved (Strong ASIC defense) |
| PBKDF2-HMAC-SHA256 | No (CPU only) | $\ge 600,000$ iterations | Legacy Acceptable (FIPS compliant) |
| SHA-256 / SHA-512 | No | None | Prohibited for credentials |