Weak Password Hashing Anti-Pattern
Severity: High
Summary
Applications use fast general-purpose hash functions (MD5, SHA-1, SHA-256) without salting for password storage, enabling rapid cracking via rainbow tables or GPU-accelerated brute-force (billions of hashes per second). Results in mass account compromise and credential stuffing attacks.
The Anti-Pattern
The anti-pattern is using cryptographic hash functions that are too fast or lack essential features like salting and adjustable work factors, making them vulnerable to offline attacks.
BAD Code Example
# VULNERABLE: Using MD5 for password hashing.
import hashlib
def hash_password_md5(password):
# MD5 is a cryptographically broken hash function.
# It is extremely fast, and rainbow tables for MD5 are widely available.
return hashlib.md5(password.encode()).hexdigest()
def verify_password_md5(password, stored_hash):
return hash_password_md5(password) == stored_hash
# Another example: plain SHA-256 without salting.
def hash_password_sha256_unsalted(password):
# SHA-256 is a strong hash for data integrity, but too fast for passwords.
# Without a salt, identical passwords result in identical hashes.
return hashlib.sha256(password.encode()).hexdigest()
# Problems:
# - Speed: MD5/SHA-256 can compute billions of hashes per second.
# - No Salt: Allows rainbow table attacks and reveals users with identical passwords.
# - No Work Factor: Cannot be slowed down to resist brute-force attacks.
GOOD Code Example
# SECURE: Use a password-hashing algorithm designed to be slow and include a unique salt.
import bcrypt # Or Argon2, scrypt
def hash_password_secure(password):
# bcrypt generates unique salt per password and supports adjustable work factor.
# Higher rounds = slower hashing = better brute-force resistance.
hashed_password = bcrypt.hashpw(password.encode('utf-8'), bcrypt.gensalt(rounds=12))
return hashed_password.decode('utf-8') # Store the hashed password as a string.
def verify_password_secure(password, stored_hash):
# checkpw() verifies password against stored hash with constant-time comparison.
# Extracts salt and work factor from stored hash to prevent timing attacks.
return bcrypt.checkpw(password.encode('utf-8'), stored_hash.encode('utf-8'))
# Recommended algorithms (in order of current preference):
# 1. Argon2id (best practice for new applications)
# 2. bcrypt
# 3. scrypt
# Always use libraries for password hashing; never implement your own.
Detection
- Code Review: Search your codebase for password hashing implementations.
- Look for
hashlib.md5(),hashlib.sha1(), orhashlib.sha256()being used for passwords. - Check if
bcrypt,argon2, orscryptlibraries are used. - Verify that a unique, cryptographically secure salt is generated for each password.
- Look for
- Database Inspection: Look at the
passwordorpassword_hashcolumn in your user database.- Are the hashes all of the same length and format? (Suggests no salt or static salt).
- Do they start with prefixes like
$2a$(bcrypt),$argon2id$(Argon2), or$s2$(scrypt)?
- Check for plaintext passwords: Ensure that passwords are never stored in plaintext.
Prevention
- Use strong, slow, adaptive password-hashing functions.
- Argon2id: Currently the recommended algorithm for new applications.
- bcrypt: A widely used and strong algorithm.
- scrypt: Another strong algorithm.
- Always use a unique, cryptographically secure salt for each password. bcrypt and Argon2 generate salts automatically.
- Adjust the work factor (cost) appropriately. Increase the number of rounds (bcrypt) or memory/time cost (Argon2) until hashing takes about 250-500 milliseconds on your server hardware. This makes brute-forcing expensive for attackers.
- Never use fast, general-purpose hash functions like MD5, SHA-1, or plain SHA-256 for passwords. These are designed for speed, not for password storage.
- Never store plaintext passwords.
Related Security Patterns & Anti-Patterns
- Hardcoded Secrets Anti-Pattern: Password hashes are sensitive data and should be protected.
- Missing Authentication Anti-Pattern: Weak password hashing undermines the entire authentication process.
- Timing Attacks Anti-Pattern: Proper password-hashing libraries use constant-time comparisons to prevent timing attacks during verification.
References
- OWASP Top 10 A04:2025 - Cryptographic Failures
- OWASP GenAI LLM10:2025 - Unbounded Consumption
- OWASP API Security API2:2023 - Broken Authentication
- OWASP Password Storage Cheat Sheet
- CWE-327: Use of a Broken or Risky Cryptographic Algorithm
- CWE-759: Use of a One-Way Hash without a Salt
- CAPEC-55: Rainbow Table Password Cracking
- PortSwigger: Authentication
- BlueKrypt - Cryptographic Key Length Recommendation
- Source: sec-context