highAuthentication

Weak Password Hashing (MD5/SHA1)

What Is This Vulnerability?

Weak password hashing means storing user passwords using fast, non-salted hash functions like MD5 or SHA1 instead of purpose-built password hashing algorithms. These fast algorithms can be brute-forced at billions of attempts per second on modern GPUs, allowing attackers who obtain the hash database to recover most passwords quickly.

Why It Happens

Developers may use MD5 or SHA1 out of familiarity, not understanding the difference between cryptographic hashing and password hashing. Legacy codebases often predate modern password hashing libraries. Some developers mistakenly believe adding a salt to MD5 makes it secure, when the fundamental problem is the hash function's speed.

Example Code

Vulnerablelib/auth.ts
import crypto from "crypto";

async function registerUser(username: string, password: string) {
  const hash = crypto.createHash("md5").update(password).digest("hex");
  await db.query(
    "INSERT INTO users (username, password_hash) VALUES ($1, $2)",
    [username, hash]
  );
}

async function verifyUser(username: string, password: string) {
  const hash = crypto.createHash("md5").update(password).digest("hex");
  const result = await db.query(
    "SELECT * FROM users WHERE username = $1 AND password_hash = $2",
    [username, hash]
  );
  return result.rows[0] ?? null;
}
Fixedlib/auth.ts
import bcrypt from "bcrypt";

const SALT_ROUNDS = 12;

async function registerUser(username: string, password: string) {
  const hash = await bcrypt.hash(password, SALT_ROUNDS);
  await db.query(
    "INSERT INTO users (username, password_hash) VALUES ($1, $2)",
    [username, hash]
  );
}

async function verifyUser(username: string, password: string) {
  const result = await db.query(
    "SELECT * FROM users WHERE username = $1",
    [username]
  );
  const user = result.rows[0];
  if (!user) return null;
  const match = await bcrypt.compare(password, user.password_hash);
  return match ? user : null;
}

How Hackers Exploit It

After obtaining a database dump (through SQL injection, a backup leak, or a compromised admin panel), attackers run the hashes through precomputed rainbow tables or GPU-accelerated cracking tools like Hashcat. MD5 hashes can be cracked at over 60 billion attempts per second on a modern GPU. Most common passwords are recovered within seconds to minutes.

How to Fix It

Use bcrypt, scrypt, or Argon2 for password hashing. These algorithms are intentionally slow and include built-in salting, making brute-force attacks computationally expensive. Set the work factor high enough that hashing takes at least 100ms per password. Migrate existing MD5/SHA1 hashes by re-hashing passwords on the next successful login.

Frequently Asked Questions

Why is MD5 bad for password hashing?
MD5 was designed to be fast, which is the opposite of what you want for password storage. A single modern GPU can compute over 60 billion MD5 hashes per second, meaning even moderately complex passwords can be cracked in minutes. MD5 also has known collision vulnerabilities.
What work factor should I use for bcrypt?
A work factor of 12 is a common starting point, which takes roughly 200-300ms per hash on modern hardware. Increase the factor as hardware gets faster. The goal is for each hash operation to take at least 100ms, making brute-force attacks impractical while keeping login times acceptable.
How do I migrate existing MD5 hashes to bcrypt?
On the next successful login, verify the password against the old MD5 hash, then re-hash it with bcrypt and update the database. Add a column to track the hash algorithm. For users who do not log in, force a password reset after a reasonable migration period.

Related Security Topics

Check Your Code for This Vulnerability

Run a free scan to check if your site is affected by weak password hashing (md5/sha1).