I audited a legacy PHP app last year and found 400,000 user passwords stored as plain MD5 hashes, no salt, no iterations. A dictionary attack against that database would crack the majority of accounts in under an hour using a cheap GPU instance. The team thought MD5 was “encrypted.” It is not. But whether MD5 is dangerous depends entirely on what you are using it for.
Short answer: MD5 is not secure for anything adversarial — password storage, digital signatures, or certificate verification. It remains fine for non-adversarial file integrity checks where no attacker can craft the input. The rest of this guide explains exactly where that line sits.
Generate hashes locally, any algorithm.
MD5, SHA-256, SHA-512, computed in your browser. No upload. Your data stays on your device.
Open Hash Generator →What MD5 Actually Is
MD5 is a cryptographic hash function designed by Ron Rivest in 1991. It takes any input (a password, a file, a string) and produces a fixed 128-bit (32 hex character) output. The same input always produces the same hash. Different inputs should (in theory) produce different hashes.
“Should” is the problem. In 2004, Xiaoyun Wang and Hongbo Yu published a paper demonstrating a practical collision attack against MD5, two different inputs that produce the same 128-bit output. By 2008, researchers at CWI Amsterdam had used that technique to forge a valid SSL certificate. The attack made front-page security news and MD5 has been deprecated for cryptographic uses since.
- 1991
MD5 designed by Ron Rivest
A 128-bit hash: any input produces a fixed 32-hex-character output, and the same input always hashes the same.
- 2004
First practical collision
Xiaoyun Wang and Hongbo Yu publish a collision attack — two different inputs that produce the same 128-bit output.
- 2008
Forged SSL certificate
CWI Amsterdam researchers exploit MD5 collisions to create a rogue certificate authority browsers would trust, forcing the CA/Browser Forum to ban MD5 in SSL.
- 2026
Broken for crypto, fine for checksums
A single RTX 4090 computes ~164 billion MD5 hashes/sec, cracking unsalted 8-char passwords in under a minute. Still safe only for non-adversarial integrity checks.
Why MD5 Is Dangerous for Passwords
MD5 was designed for speed. That is exactly the wrong property for password hashing.
A single NVIDIA RTX 4090 can compute approximately164 billion MD5 hashes per second(measured with Hashcat). Against an unsalted MD5 hash database:
| Password Type | Crack Time (RTX 4090, MD5) | Crack Time (RTX 4090, bcrypt cost 12) |
|---|---|---|
| 6-char alphanumeric | Instant (precomputed) | 3 weeks |
| 8-char alphanumeric | Under 1 minute | 200 years |
| 8-char with symbols | ~2 hours | Millions of years |
| 12-char random | Days to weeks | Heat death of universe |
bcrypt's “cost factor” controls how many internal rounds it runs. At cost 12, each bcrypt computation takes roughly 300ms, the same GPU that cracks MD5 at 164 billion/second is limited to around 1,800 bcrypt hashes per second. That 90-million-fold slowdown is the entire point.
The Collision Attack: What It Actually Means
A collision means two different inputs hash to the same output. For password hashing, this is less of a concern than raw speed, an attacker is not trying to find a collision, they are just computing hashes of common passwords until one matches your stored hash.
Where collisions are catastrophic is in digital signatures and certificate verification. The 2008 rogue CA certificate attackexploited MD5 collisions to create a fraudulent certificate authority that browsers would trust. That attack is what forced the CA/Browser Forum to ban MD5 in SSL certificates.
The formal verdict is RFC 6151, which updates the original MD5 specification (RFC 1321) and concludes that MD5 is “no longer acceptable” wherever collision resistance is required, such as digital signatures and certificate verification.
Where MD5 Is Still Fine
MD5 remains appropriate in situations where there is no adversary trying to craft a malicious input, specifically, non-cryptographic integrity checks.
File corruption detection: Checking that a file downloaded intact from a known-good source. You are not defending against an attacker, you are verifying that the network did not garble bytes in transit. MD5 catches that reliably. If you want to verify a downloaded file against a published MD5 checksum right now, do it locally with our File Checksum Verifier — the file never leaves your device.
Database deduplication: Detecting duplicate records in a dataset. If two rows produce the same MD5, they are almost certainly identical. The theoretical collision risk at any realistic dataset size is negligible for this purpose.
Cache keys: Generating short, deterministic identifiers for cache entries in systems you control. No adversary is crafting collisions against your Redis cache keys.
| Use Case | MD5 Safe? | Use Instead |
|---|---|---|
| Password storage | No — ever | bcrypt, scrypt, Argon2id |
| Digital signatures | No | SHA-256 or SHA-3 |
| SSL / TLS certificates | Banned | SHA-256 (required by CA/B Forum) |
| HMAC / API authentication | No | HMAC-SHA256 |
| File download integrity check | Yes (non-adversarial) | SHA-256 preferred if available |
| Deduplication / cache keys | Yes | xxHash or MD5 — both fine here |
What Correct Password Hashing Looks Like
1import bcrypt from 'bcryptjs';2 3// STORING a password4const COST_FACTOR = 12; // ~300ms on modern hardware — adjust based on your server5const hash = await bcrypt.hash(plaintextPassword, COST_FACTOR);6// Store 'hash' in your database — never store plaintext or MD57 8// VERIFYING a password at login9const isValid = await bcrypt.compare(plaintextPassword, storedHash);10// Returns true/false — timing-safe by default in bcryptjs11 12// BAD — never do this13const badHash = require('crypto')14 .createHash('md5')15 .update(plaintextPassword)16 .digest('hex');17// This is what 400,000 accounts in that PHP app looked like1// For Python (Django and Flask both default to PBKDF2 + SHA256)2from argon2 import PasswordHasher3 4ph = PasswordHasher(time_cost=2, memory_cost=65536, parallelism=2)5 6# Store7hash = ph.hash(plaintext_password)8 9# Verify (raises VerifyMismatchError if wrong)10ph.verify(hash, plaintext_password)Common Mistakes When Migrating Away from MD5
Missing Salt: Still Broken
Adding a salt does not fix MD5's speed problem, it only defeats precomputed rainbow tables. A salted MD5 still runs at 164 billion computations/second on that GPU. The attacker just has to brute-force each hash individually rather than looking it up. For long random passwords, this is slow. For common passwords, it is still fast.
Salt prevents rainbow tables. It does not prevent brute force. Only a slow algorithm prevents brute force.
Safe Migration: Rehash on Next Login
You cannot convert existing MD5 hashes to bcrypt because you do not have the original passwords. The right migration pattern is to rehash passwords as users log in:
1async function login(plaintext: string, storedHash: string) {2 // Detect if this is an old MD5 hash (32 hex chars, no bcrypt prefix)3 const isMD5 = /^[a-f0-9]{32}$/.test(storedHash);4Â 5 if (isMD5) {6 // Verify the old MD5 hash (temporary compatibility)7 const md5 = require('crypto').createHash('md5').update(plaintext).digest('hex');8 if (md5 !== storedHash) return false;9Â 10 // Immediately upgrade to bcrypt on successful login11 const newHash = await bcrypt.hash(plaintext, 12);12 await db.users.updateHash(userId, newHash);13 return true;14 }15Â 16 // Standard bcrypt path for already-migrated users17 return bcrypt.compare(plaintext, storedHash);18}SHA-256 Is Not a Password Hashing Algorithm
SHA-256 is far more secure than MD5 for signatures and certificate work. But it is still too fast for passwords, around 10 billion SHA-256 hashes per second on the same GPU. Do not substitute SHA-256 for bcrypt or Argon2 when storing passwords. They are different tools solving different problems.
My Recommendation
If you are storing passwords: use Argon2id. It is theOWASP-recommendedfirst choice as of 2023. If Argon2 is not available in your stack, bcrypt with cost 12 is fine. If you are on a legacy system you cannot change, add salt and upgrade accounts on next login.
If you are using MD5 for checksums or dedup: it is fine. Just make sure there is no attacker in that pipeline. The moment you are checking trust boundaries, authentication, token validation, signature verification, drop it.
For generating hashes locally without sending data to a server, try our offline hash generator. And if you are rethinking your overall security approach, our password security guide covers the full stack, from storage algorithms to breach notification handling.
Hash files or strings locally.
MD5, SHA-256, SHA-512, all computed in-browser. Nothing leaves your device.
Open Hash Generator →