
weak-password-hashing-anti-pattern
Security anti-pattern for weak password hashing (CWE-327, CWE-759). Use when generating or reviewing code that stores or verifies user passwords. Detects use of MD5, SHA1, SHA256 without salt, or missing password hashing entirely. Recommends bcrypt, Argon2, or scrypt.
"Security anti-pattern for weak password hashing (CWE-327, CWE-759). Use when generating or reviewing code that stores or verifies user passwords. Detects use of MD5, SHA1, SHA256 without salt, or missing password hashing entirely. Recommends bcrypt, Argon2, or scrypt."
Weak Password Hashing Anti-Pattern
Severity: High
Summary
Weak password hashing is a critical vulnerability that occurs when applications use cryptographic algorithms that are unsuitable for storing passwords. This anti-pattern often involves using fast, general-purpose hash functions (like MD5, SHA-1, or plain SHA-256) without proper salting, or by not hashing passwords at all. Attackers can exploit this to rapidly crack user passwords using pre-computed tables (rainbow tables) or brute-force attacks, especially with modern GPUs. This can lead to mass account compromise and credential stuffing across different services.
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 automatically generates a unique salt for each password.
# The `gensalt()` function also allows specifying the work factor (rounds).
# A higher work factor makes hashing slower, increasing resistance to brute-force attacks.
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()` safely verifies the password against the stored hash.
# It extracts the salt and work factor from the stored hash and performs a
# constant-time comparison 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. Modern algorithms like bcrypt and Argon2 handle salt generation 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
- Source: sec-context
You Might Also Like
Related Skills

coding-agent
Run Codex CLI, Claude Code, OpenCode, or Pi Coding Agent via background process for programmatic control.
openclaw
add-uint-support
Add unsigned integer (uint) type support to PyTorch operators by updating AT_DISPATCH macros. Use when adding support for uint16, uint32, uint64 types to operators, kernels, or when user mentions enabling unsigned types, barebones unsigned types, or uint support.
pytorch
at-dispatch-v2
Convert PyTorch AT_DISPATCH macros to AT_DISPATCH_V2 format in ATen C++ code. Use when porting AT_DISPATCH_ALL_TYPES_AND*, AT_DISPATCH_FLOATING_TYPES*, or other dispatch macros to the new v2 API. For ATen kernel files, CUDA kernels, and native operator implementations.
pytorch
skill-writer
Guide users through creating Agent Skills for Claude Code. Use when the user wants to create, write, author, or design a new Skill, or needs help with SKILL.md files, frontmatter, or skill structure.
pytorch
implementing-jsc-classes-cpp
Implements JavaScript classes in C++ using JavaScriptCore. Use when creating new JS classes with C++ bindings, prototypes, or constructors.
oven-sh
implementing-jsc-classes-zig
Creates JavaScript classes using Bun's Zig bindings generator (.classes.ts). Use when implementing new JS APIs in Zig with JSC integration.
oven-sh