TL;DR
- AI editors still reach for md5/sha1 to hash passwords because that is what a decade of tutorials taught them
- Fast hashes are the whole problem: they let attackers brute force leaked password databases at billions of guesses per second
- Swap to bcrypt or argon2id. Two lines. Do it before you ship.
I asked Cursor to add a signup endpoint to a side project last week. Clean code, worked first try. Then I read the part where it stored the password: one line, hashlib.md5. No salt. No work factor. Just a raw MD5 digest sitting in my users table.
I have seen this enough times now that I stopped being surprised. Claude Code does it. Copilot does it. Ask any AI editor to "hash the password before saving" and there is a real chance you get MD5 or SHA-1 back.
The vulnerable code
import hashlib
# CWE-916: fast hash, no salt, no work factor
def store_password(password):
return hashlib.md5(password.encode()).hexdigest()
Sometimes it is SHA-1 instead. Sometimes SHA-256. It does not matter. They are all built to be fast, and speed is exactly what you do not want here.
Why this keeps happening
The training data. MD5 password hashing was standard advice on StackOverflow and in PHP tutorials for the better part of a decade. Millions of examples. The models learned the pattern because the pattern was everywhere. They do not know that in 2012 LinkedIn leaked 6.5 million unsalted SHA-1 passwords and most were cracked within days. They just know that "hash a password" and "md5" show up together a lot.
The math is the real problem. A modern GPU rig computes billions of MD5 hashes per second. If your user database leaks, and databases leak, every common password falls almost instantly. No salt means identical passwords produce identical hashes, so one rainbow table cracks your whole table at once.
The fix
Use a slow hash built for passwords: bcrypt, scrypt, or argon2id. They are deliberately expensive to compute, which barely matters for a single login but wrecks an attacker running billions of guesses.
import bcrypt
# salted, slow by design, tuneable work factor
def store_password(password):
return bcrypt.hashpw(password.encode(), bcrypt.gensalt(rounds=12))
def verify_password(password, stored):
return bcrypt.checkpw(password.encode(), stored)
bcrypt salts automatically, so you never manage it yourself. rounds=12 is a sane default in 2026. If you want the current best practice, argon2id via the argon2-cffi library is the OWASP-recommended choice.
Install and you are done:
pip install bcrypt
The catch: your AI editor will not flag the MD5 version. It is valid Python. It runs. It passes your tests. Nothing tells you it is wrong until the breach.
I have been running SafeWeave for exactly this. It hooks into Cursor and Claude Code as an MCP server and flags weak crypto like md5/sha1 password hashing before I move on to the next file. That said, even a basic pre-commit hook with semgrep will catch most of it. The important thing is catching it while you still have the context, whatever tool you use.
Top comments (0)