Authentication Security

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.

In short

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

Mistake 1: Fast Cryptographic Hashes (SHA-256, MD5, SHA-512)

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.

Remedy: Deploy memory-hard Argon2id (RFC 9106) or bcrypt. Memory requirements (64 MB per hash) saturate the GPU VRAM bus, collapsing crack throughput by a factor of 1,000,000x.
Mistake 2: Missing or Static Salts (Rainbow Tables & Group Cracking)

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.

Remedy: Generate a unique, cryptographically secure 16-byte random salt for every user. Standard password libraries embed the salt directly into the resulting modular crypt hash string.
Mistake 3: Under-Tuned Work Cost Factors

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.

Remedy: Benchmark production servers to target 250ms to 500ms per verification. For bcrypt, use cost factor 12 or higher. For Argon2id, use m = 65,536 KiB, t = 3, p = 4.
Mistake 4: Missing Modular Crypt Format (Hardcoded Schemes)

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.

Remedy: Store standard Modular Crypt Format (MCF) strings ($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.
Mistake 5: Client-Side Only Hashing

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.

Remedy: Transmit passwords over TLS/HTTPS directly to the server, which then applies the slow memory-hard KDF against the user salt.

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:

Empirical GPU Throughput Comparison (RTX 4090 Hashcat)
MD5 / NTLM
~120 GH/s
120 billion/sec
SHA-256
~8.5 GH/s
8.5 billion/sec
bcrypt (cost=12)
~1,500 H/s
1.5 thousand/sec
Argon2id (64MB)
~12 H/s
12 attempts/sec

Argon2id reduces brute-force efficiency by over 700,000,000x compared to SHA-256, neutralizing GPU clusters.

Production Upgrade Pattern: Re-hash on Login (Node.js & Python)
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

Misconception 1: "SHA-512 is safe because 512 bits is more secure than bcrypt"

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.

Misconception 2: "Salting makes weak passwords impossible to crack"

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.

Misconception 3: "Hashing 10,000 times in a for-loop turns SHA-256 into PBKDF2"

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