How Hackers Crack Passwords: Hashcat, Rainbow Tables & Why bcrypt Isn't Enough
The Reality of Password Cracking in 2026
An NVIDIA RTX 5090 can compute 164 billion MD5 hashes per second. That means every possible 8-character password using letters, numbers, and symbols can be cracked in under 3 hours.
Cracking Speed by Hash Algorithm (RTX 5090)
| Algorithm | Speed | Time for 8-char password |
|---|---|---|
| MD5 | 164B hashes/sec | ~2.5 hours |
| SHA-1 | 52B hashes/sec | ~8 hours |
| SHA-256 | 22B hashes/sec | ~19 hours |
| NTLM (Windows) | 280B hashes/sec | ~1.5 hours |
| bcrypt (cost 12) | 32K hashes/sec | ~2,800 years |
| Argon2id | 8K hashes/sec | ~11,200 years |
Key insight: The hash algorithm matters more than password complexity. MD5 with a 20-character password is still weaker than bcrypt with a 10-character password.
Attack Type 1: Dictionary Attack
The simplest and most effective attack. Uses wordlists of common passwords, leaked credentials, and language dictionaries.
# Hashcat dictionary attack
hashcat -m 0 -a 0 hashes.txt rockyou.txt
# -m 0 = MD5
# -a 0 = Dictionary attack
# rockyou.txt = 14 million leaked passwords
Top 10 wordlists:
- RockYou — 14M passwords from 2009 breach
- SecLists — Curated collection by Daniel Miessler
- CrackStation — 1.5B words from all major breaches
- Have I Been Pwned passwords — 900M+ unique passwords
- Weakpass — Multi-terabyte wordlists
Attack Type 2: Rule-Based Mutations
Applies transformations to dictionary words — capitalizing, appending numbers, leetspeak substitutions.
# Hashcat with rules
hashcat -m 0 -a 0 hashes.txt rockyou.txt -r rules/best64.rule
# Common rule transformations:
# password → Password (capitalize first)
# password → password123 (append numbers)
# password → p@ssw0rd (leetspeak)
# password → drowssap (reverse)
# password → Password! (capitalize + append symbol)
# password → PASSWORD (uppercase all)
Popular rule files:
- best64.rule — 64 most effective rules
- d3ad0ne.rule — 35K rules, very thorough
- OneRuleToRuleThemAll — 52K expertly curated rules
Attack Type 3: Mask/Brute Force
When you know the password pattern:
# Try all 8-character combinations: uppercase + lowercase + digits
hashcat -m 0 -a 3 hashes.txt ?a?a?a?a?a?a?a?a
# Masks:
# ?l = lowercase ?u = uppercase ?d = digit ?s = symbol
# ?a = all characters
# Common patterns people use:
hashcat -m 0 -a 3 hashes.txt ?u?l?l?l?l?l?d?d # Word + 2 digits
hashcat -m 0 -a 3 hashes.txt ?u?l?l?l?l?l?l?s # Word + symbol
hashcat -m 0 -a 3 hashes.txt ?d?d?d?d?d?d?d?d # 8-digit PIN
Attack Type 4: Rainbow Tables
Pre-computed hash → password lookup tables. Instant cracking but requires massive storage.
| Charset | Length | Table Size | Lookup Time |
|---|---|---|---|
| a-z, 0-9 | 1-8 | 460GB | Instant |
| a-z, A-Z, 0-9 | 1-7 | 1.2TB | Instant |
| All printable | 1-7 | 3TB | Instant |
Defense against rainbow tables: Salt every hash.
// ❌ No salt — vulnerable to rainbow tables
const hash = crypto.createHash('sha256').update(password).digest('hex');
// ✅ Random salt — rainbow tables are useless
const salt = crypto.randomBytes(16).toString('hex');
const hash = crypto.createHash('sha256').update(salt + password).digest('hex');
// Store both salt and hash
// ✅ Best: bcrypt includes salt automatically
const hash = await bcrypt.hash(password, 12); // Salt is embedded in the hash
Why bcrypt Alone Isn't Enough in 2026
bcrypt's max input is 72 bytes. Longer passwords are silently truncated.
// These produce THE SAME hash:
await bcrypt.hash('A'.repeat(72), 12);
await bcrypt.hash('A'.repeat(72) + 'ANYTHING_ELSE', 12); // Truncated!
Solution: Pre-hash with SHA-256 before bcrypt (or use Argon2id)
// ✅ Pre-hash to handle any length password
import { createHash } from 'crypto';
import bcrypt from 'bcrypt';
function secureHash(password: string): Promise<string> {
// SHA-256 the password first (produces 64 hex chars, well under 72 bytes)
const preHash = createHash('sha256').update(password).digest('hex');
return bcrypt.hash(preHash, 12);
}
// ✅ Even better: Use Argon2id (no length limit, memory-hard)
import argon2 from 'argon2';
const hash = await argon2.hash(password, {
type: argon2.argon2id,
memoryCost: 65536, // 64MB
timeCost: 3,
parallelism: 4,
});
What Actually Makes Passwords Secure
Forget complexity rules. Length and randomness are what matter:
| Password | Entropy | Time to Crack (bcrypt cost 12) |
|---|---|---|
| P@ssw0rd | ~30 bits | In dictionary, instant |
| correct horse battery staple | ~44 bits | ~1 year |
| kj#8Fm2p$Lq9 | ~78 bits | Effectively never |
| 5 random words (diceware) | ~65 bits | ~10,000 years |
Modern Password Policy Recommendations (NIST 800-63B)
- ✅ Minimum 8 characters (12+ recommended)
- ✅ Check against breached password lists (HaveIBeenPwned API)
- ✅ Allow all characters including spaces and Unicode
- ❌ No forced complexity rules (uppercase + number + symbol)
- ❌ No forced rotation (unless breach suspected)
- ❌ No security questions (easily researched)
// Modern password validation
import { pwnedPassword } from 'hibp';
async function validatePassword(password: string): Promise<string[]> {
const errors: string[] = [];
if (password.length < 12) errors.push('Password must be at least 12 characters');
if (password.length > 128) errors.push('Password must be under 128 characters');
// Check breached passwords
const breachCount = await pwnedPassword(password);
if (breachCount > 0) {
errors.push(\`This password appeared in ${breachCount} data breaches. Choose a different one.\`);
}
return errors;
}
The most important takeaway: Use Argon2id, enforce minimum length, check breached lists, and stop requiring special characters. That's it.
Advertisement
Free Security Tools
Try our tools now
Expert Services
Get professional help
OWASP Top 10
Learn the top risks
Related Articles
Threat Modeling for Developers: STRIDE, PASTA & DREAD with Practical Examples
Threat modeling is the most cost-effective security activity — finding design flaws before writing code. This guide covers STRIDE, PASTA, and DREAD methodologies with real-world examples for web, API, and cloud applications.
Building a Security Champions Program: Scaling Security Across Dev Teams
Security teams can't review every line of code. Security Champions embed security expertise in every development team. This guide covers program design, champion selection, training, metrics, and sustaining engagement.
The Ultimate Secure Code Review Checklist for 2025
A comprehensive, language-agnostic checklist for secure code reviews. Use this as your team's standard for catching vulnerabilities before they reach production.