Beginner

Password Hashing: What NOT to Do

Learn from common mistakes. Understand why certain approaches fail and how to implement secure password storage correctly.

The Golden Rule

✓ What TO Do

Use bcrypt, Argon2, or scrypt for password hashing. These are specifically designed for passwords and include built-in salting and configurable work factors.

Now let's explore what NOT to do and why these mistakes are dangerous.

Mistake #1: Storing Plaintext Passwords

❌ Never Do This
INSERT INTO users (username, password) VALUES ('alice', 'MyPassword123');

Why it's dangerous:

  • -Database breach exposes all passwords immediately
  • -Employees with database access can see passwords
  • -Users who reuse passwords are compromised on other sites
  • -Violates GDPR, CCPA, and most security compliance standards
Real-world example:

In 2019, Facebook admitted storing hundreds of millions of passwords in plaintext, accessible to 20,000+ employees. This is a catastrophic security failure from a company with unlimited resources.

Mistake #2: Using Fast Hash Functions

❌ Never Do This
password_hash = SHA256(password)  // WRONG!
password_hash = MD5(password)     // EVEN WORSE!

Why it's dangerous:

  • -Too fast: Attackers can try billions of passwords per second
  • -GPU acceleration: Modern GPUs can compute 100+ billion MD5 hashes per second
  • -Rainbow tables: Precomputed tables make cracking instant for common passwords
Attack speed comparison:
MD5: 100 billion hashes/second (GPU)
SHA-256: 50 billion hashes/second (GPU)
bcrypt (cost=12): ~5 hashes/second (GPU)
Argon2: ~0.5 hashes/second (GPU)

Bcrypt is 10 billion times slower than MD5, making brute-force attacks impractical.

Mistake #3: Not Using Salt

❌ Never Do This
password_hash = SHA256(password)  // No salt - WRONG!
password_hash = MD5(password)     // No salt - WRONG!

Why it's dangerous:

  • -Identical passwords: Users with the same password have the same hash
  • -Rainbow tables: Precomputed tables work against all users
  • -Pattern detection: Attackers can identify common passwords by hash frequency
Without salt:
alice: password123 → 5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8
bob:   password123 → 5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8
carol: password123 → 5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8

Attacker cracks one, cracks all three users with the same password.

With unique salts:
alice: password123 + salt_a → 8f3d9e2a1b4c5d6e7f8a9b0c1d2e3f4a5b6c7d8e9f0a1b2c3d4e5f6a7b8c9d0e
bob:   password123 + salt_b → 1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b
carol: password123 + salt_c → 9f8e7d6c5b4a3f2e1d0c9b8a7f6e5d4c3b2a1f0e9d8c7b6a5f4e3d2c1b0a9f8e

Each user requires a separate attack. Rainbow tables are useless.

Mistake #4: Using a Global Salt

❌ Never Do This
GLOBAL_SALT = "my-app-secret-salt-2024"
password_hash = SHA256(GLOBAL_SALT + password)  // WRONG!

Why it's dangerous:

  • -If the salt leaks (source code, config file), all passwords are vulnerable
  • -Attackers can build rainbow tables specifically for your salt
  • -Same password still produces the same hash across users
✓ Correct Approach

Generate a unique random salt for each password. Store the salt alongside the hash in the database. Modern password hashing functions (bcrypt, Argon2) handle this automatically.

Mistake #5: Rolling Your Own Crypto

❌ Never Do This
// "I'll make it more secure by hashing multiple times!"
password_hash = SHA256(SHA256(SHA256(password)))  // WRONG!

// "I'll add my own salt and pepper!"
password_hash = SHA256(salt + password + pepper + timestamp)  // WRONG!

Why it's dangerous:

  • -Cryptography is hard. Experts spend years designing secure algorithms
  • -Your custom scheme likely has vulnerabilities you don't know about
  • -Multiple SHA-256 rounds are still fast-GPUs can do billions per second
  • -You're not smarter than the cryptographers who designed bcrypt/Argon2
✓ Use Battle-Tested Libraries

Use bcrypt, Argon2, or scrypt. These have been analyzed by cryptographers worldwide and are proven secure.

Mistake #6: Low Work Factor

❌ Never Do This
// bcrypt with cost=4 (way too fast!)
password_hash = bcrypt.hashpw(password, bcrypt.gensalt(rounds=4))

Why it's dangerous:

  • -Low cost factors make hashing too fast, enabling brute-force attacks
  • -Hardware gets faster every year-what's slow today is fast tomorrow
✓ Recommended Work Factors (2024)
bcrypt: cost=12 or higher (takes ~250ms per hash)
Argon2id: memory=64MB, iterations=3, parallelism=4
scrypt: N=2^16, r=8, p=1

Target: 250-500ms per hash. Slow enough to deter attacks, fast enough for user experience.

The Correct Way

Python (bcrypt):
import bcrypt

# Hash password
password = b"user_password"
hashed = bcrypt.hashpw(password, bcrypt.gensalt(rounds=12))

# Verify password
if bcrypt.checkpw(password, hashed):
  print("Password correct!")
else:
  print("Password incorrect!")
Node.js (bcrypt):
const bcrypt = require('bcrypt');
const saltRounds = 12;

// Hash password
const hash = await bcrypt.hash('user_password', saltRounds);

// Verify password
const match = await bcrypt.compare('user_password', hash);
if (match) {
  console.log('Password correct!');
}
PHP (password_hash):
// Hash password (uses bcrypt by default)
$hash = password_hash('user_password', PASSWORD_DEFAULT);

// Verify password
if (password_verify('user_password', $hash)) {
  echo 'Password correct!';
}

Security Checklist

Use bcrypt, Argon2, or scrypt (NOT SHA-256, MD5, or SHA-1)
Use unique random salt for each password (automatic in bcrypt/Argon2)
Set appropriate work factor (bcrypt cost=12+, Argon2 memory=64MB+)
Never store plaintext passwords
Use constant-time comparison when verifying passwords
Implement rate limiting on login attempts
Use HTTPS to protect passwords in transit
Enforce minimum password strength requirements

Try It Yourself

See the difference between fast and slow hashing:

Exercise: Compare Hash Speeds
  1. 1. Go to the Hash Calculator
  2. 2. Enter a password: MySecurePassword123
  3. 3. Select SHA-256 and note how fast it computes (instant)
  4. 4. Now imagine an attacker trying billions of these per second
  5. 5. This is why SHA-256 is NOT suitable for passwords
  6. 6. bcrypt with cost=12 would take ~250ms-10 billion times slower