Skip to main content

Free 30-min security demo  — We'll scan your real code and show live findings, no commitment Book Now

Offensive360
Home / Knowledge Base / Weak Password Hashing
High CWE-916 A02:2021 Cryptographic Failures

Weak Password Hashing

Storing passwords with weak or fast hash algorithms (MD5, SHA-1, unsalted SHA-256) allows attackers to crack them rapidly after a database breach. Learn proper password hashing with bcrypt, Argon2, and PBKDF2.

Affects: C#JavaPythonPHPJavaScriptRubyGo

What is Weak Password Hashing?

Weak password hashing occurs when an application stores user passwords using cryptographic hash functions that are too fast (MD5, SHA-1, SHA-256, SHA-512), stores them in plaintext, or uses hashing without a unique per-password salt. When an attacker obtains a database dump, they can crack weak hashes at billions of guesses per second using GPU-accelerated tools.

MD5 hashes can be cracked at ~200 billion hashes/sec on commodity hardware. An 8-character password database hashed with MD5 can be fully cracked in minutes. Password-specific algorithms (bcrypt, Argon2, PBKDF2) are intentionally slow and memory-hard, making large-scale cracking computationally infeasible.

How exploitation works

A database breach exposes a password table:

| username | password_hash                    |
|----------|----------------------------------|
| alice    | 5f4dcc3b5aa765d61d8327deb882cf99 |  ← MD5("password")
| bob      | 202cb962ac59075b964b07152d234b70 |  ← MD5("123")

An attacker runs these hashes against a precomputed rainbow table or dictionary attack tool. Common passwords like “password” and “123” are cracked instantly. Without salt, identical passwords produce identical hashes — cracking one hash cracks all accounts with the same password.

Vulnerable code examples

C# — MD5 password hash

// VULNERABLE: MD5 is a fast hash — trivially crackable
public string HashPassword(string password)
{
    using var md5 = MD5.Create();
    byte[] hash = md5.ComputeHash(Encoding.UTF8.GetBytes(password));
    return Convert.ToHexString(hash);
}

Java — SHA-256 without salt

// VULNERABLE: Even SHA-256 without per-user salt is vulnerable to rainbow tables
public String hashPassword(String password) throws Exception {
    MessageDigest digest = MessageDigest.getInstance("SHA-256");
    byte[] hash = digest.digest(password.getBytes(StandardCharsets.UTF_8));
    return Base64.getEncoder().encodeToString(hash);
}

PHP — md5/sha1

// VULNERABLE: Never use md5() or sha1() for passwords
$hash = md5($password);
$hash = sha1($password);

Secure code examples

C# — ASP.NET Core Identity (BCrypt)

// SECURE: ASP.NET Core Identity uses PBKDF2 with 100,000 iterations by default
// Or use BCrypt.Net-Next directly:
using BCrypt.Net;

public string HashPassword(string password)
{
    return BCrypt.HashPassword(password, workFactor: 12); // Cost factor 12 ≈ 250ms
}

public bool VerifyPassword(string password, string hash)
{
    return BCrypt.Verify(password, hash);
}

Java — BCrypt with Spring Security

// SECURE: BCryptPasswordEncoder is the Spring Security standard
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;

@Bean
public PasswordEncoder passwordEncoder() {
    return new BCryptPasswordEncoder(12); // Cost factor 12
}

// Usage:
String hash = passwordEncoder.encode(rawPassword);
boolean valid = passwordEncoder.matches(rawPassword, storedHash);

Python — Argon2

# SECURE: Argon2 is the winner of the Password Hashing Competition
# pip install argon2-cffi
from argon2 import PasswordHasher

ph = PasswordHasher(time_cost=3, memory_cost=65536, parallelism=4)

def hash_password(password: str) -> str:
    return ph.hash(password)

def verify_password(stored_hash: str, password: str) -> bool:
    return ph.verify(stored_hash, password)

PHP — password_hash

// SECURE: PHP's built-in password_hash uses bcrypt by default
$hash = password_hash($password, PASSWORD_ARGON2ID, [
    'memory_cost' => 65536,
    'time_cost'   => 4,
    'threads'     => 1,
]);
$valid = password_verify($password, $hash);

What Offensive360 detects

  • MD5/SHA-1/SHA-2 for passwords — Direct use of fast hash functions on password values
  • Missing salt — Hash operations on passwords without a per-password random salt
  • Plaintext password storage — Passwords written to databases, logs, or files without hashing
  • Hardcoded or static salts — Salt values that are constant across users, negating rainbow table protection
  • Custom password hashing — Homegrown schemes that avoid established password hashing algorithms

Remediation guidance

  1. Use a dedicated password hashing algorithm — Argon2id (recommended), bcrypt, or PBKDF2. Never use MD5, SHA-1, or SHA-256 for passwords.

  2. Use your framework’s built-in password library — ASP.NET Core Identity, Spring Security’s BCryptPasswordEncoder, Django’s make_password, and PHP’s password_hash() are all correct by default.

  3. Set an appropriate work factor — bcrypt cost 12, Argon2id with 64MB memory and 3 iterations, or PBKDF2 with 600,000 iterations (NIST 2023 recommendation).

  4. Migrate legacy hashes — On next login, detect old hash format, verify the plaintext against the old hash, then re-hash with the new algorithm.

  5. Never log passwords — Ensure passwords are not written to logs, error messages, or debug output at any point in the authentication flow.

References

By Offensive360 Security Research Reviewed: March 2026

Detect Weak Password Hashing automatically

Run Offensive360 SAST on your codebase to find this and 100+ other vulnerabilities.