You need Python 3 and a terminal — nothing else. Everything runs
against hashes you create on your own machine, so there is no real account anywhere
near this. Check with python3 --version. To try the professional tools later,
install John the Ripper (sudo apt install john) or Hashcat
(sudo apt install hashcat); neither is present in the environment that
produced this page, so the captured output below comes from a dictionary attack written in
Python — the same technique, just slower.
🔴 Crack only hashes you are authorised to crack. Cracking the password hashes of an account or system you do not own is illegal and is not what this teaches. The purpose here is defensive: to feel, first-hand, why weak passwords and bad hashing fall in milliseconds — so you never ship them.
Understanding Password Attacks
Password attacks are among the most common and effective techniques used by attackers to gain unauthorized access to systems, applications, and data. Despite decades of security awareness efforts, weak and reused passwords remain one of the largest attack surfaces in any organization.
Stolen or weak credentials remain among the most common ways attackers gain initial access. Understanding how these attacks work is essential for both penetration testers (who simulate them with authorization) and defenders (who must protect against them).
The techniques described in this tutorial are for educational purposes and authorized security testing only. Unauthorized use of password cracking tools against accounts or systems you do not own violates computer crime laws in every jurisdiction. Always obtain explicit written permission before testing.
Brute Force Attacks
A brute force attack systematically tries every possible combination of characters until the correct password is found. It is the most straightforward but also the most computationally expensive attack method.
How It Works
The attacker's tool generates every permutation of characters within a defined character set and length range. For example, a brute force against a 4-character numeric PIN would try 0000, 0001, 0002, all the way to 9999 -- a total of 10,000 combinations.
Time and Complexity
The time required for a brute force attack grows exponentially with password length and character set size:
Password: 4 lowercase letters (a-z)
Combinations: 26^4 = 456,976
At 1 billion guesses/second: instant
Password: 8 lowercase letters (a-z)
Combinations: 26^8 = 208,827,064,576
At 1 billion guesses/second: ~3.5 minutes
Password: 8 mixed case + digits + symbols (95 printable ASCII)
Combinations: 95^8 = 6,634,204,312,890,625
At 1 billion guesses/second: ~76 days
Password: 12 mixed case + digits + symbols
Combinations: 95^12 = ~5.4 x 10^23
At 1 billion guesses/second: ~17 million years
A 12-character lowercase passphrase like "correcthorsebatterystaple" has more combinations than an 8-character password using every character type. This is why modern security guidance emphasizes password length over arbitrary complexity requirements. Each additional character multiplies the search space exponentially.
Tools Used in Brute Force
- Hydra -- fast online brute forcer supporting SSH, FTP, HTTP, RDP, SMB, and dozens of other protocols
- John the Ripper -- offline password cracker that can brute force password hashes
- Hashcat -- GPU-accelerated hash cracker capable of billions of guesses per second
# Example: Hydra brute force against SSH (authorized testing only)
hydra -l admin -x 4:6:aA1 192.168.1.50 ssh
# -l admin = username to target
# -x 4:6:aA1 = brute force mode: min 4 chars, max 6 chars,
# using lowercase (a), uppercase (A), and digits (1)
Dictionary Attacks
A dictionary attack uses a pre-compiled list of likely passwords rather than trying every possible combination. This is far more efficient than brute force because most people choose predictable passwords.
Common Wordlists
Security researchers compile wordlists from real data breaches, common passwords, and linguistic patterns. Some widely used lists include:
- rockyou.txt -- 14 million passwords from the 2009 RockYou breach; the most common starting wordlist
- SecLists -- a curated collection of wordlists for multiple attack types, maintained by Daniel Miessler
- CrackStation -- a massive wordlist containing over 1.4 billion entries from multiple breach compilations
# Example: John the Ripper with a wordlist (offline hash cracking)
john --wordlist=/usr/share/wordlists/rockyou.txt hashes.txt
# Example: Hydra dictionary attack on an HTTP login form
hydra -l admin -P /usr/share/wordlists/rockyou.txt \
192.168.1.50 http-post-form \
"/login:user=^USER^&pass=^PASS^:Invalid credentials"
Rule-Based Mutations
Modern password crackers can apply transformation rules to each word in the dictionary, dramatically increasing coverage without the full cost of brute force:
# Hashcat rule-based attack
# Takes each word in the wordlist and applies mutations:
# "password" becomes: Password, PASSWORD, p@ssword, password1,
# password!, Password123, p@ssw0rd, etc.
hashcat -m 0 -a 0 hashes.txt wordlist.txt -r rules/best64.rule
# -m 0 = hash type MD5
# -a 0 = dictionary attack mode
# -r = apply the rule file
Studies of breached password databases consistently show that people follow predictable patterns: using common words, appending numbers or exclamation marks, capitalizing only the first letter, and replacing letters with obvious substitutions (@ for a, 0 for o). Rule-based dictionary attacks exploit these exact patterns, cracking the majority of human-chosen passwords in minutes.
Credential Stuffing
Credential stuffing is an automated attack that uses username-password pairs stolen from one data breach to attempt login on other services. It exploits the widespread habit of password reuse across multiple accounts.
How It Works
- Attacker obtains a list of credentials from a data breach (e.g., email/password pairs from a compromised website)
- Automated tools test each credential pair against other services (banking sites, email providers, social media)
- Because many people reuse passwords, a significant percentage of the stolen credentials work on other sites
- Successful logins give the attacker access to additional accounts belonging to the victim
Credential stuffing is not technically "cracking" a password -- the attacker already has the plaintext password. The attack succeeds because users reuse the same password on multiple services. Industry reports estimate that credential stuffing attacks have a success rate of 0.1% to 2%, which translates to thousands of compromised accounts when millions of credentials are tested.
If you use the same password for your email and a small forum that gets breached, attackers will automatically test that password against Gmail, Outlook, banking sites, and hundreds of other services within hours. A single breach can cascade into a complete takeover of your digital life. Use a unique password for every account.
Password Spraying
Password spraying is a targeted attack that takes the opposite approach from brute force. Instead of trying many passwords against one account (which triggers lockouts), it tries one or a few common passwords against many accounts simultaneously.
How It Works
# Conceptual example of password spraying logic:
# Instead of:
# admin: password1, password2, password3... (triggers lockout)
#
# The attacker does:
# user1: Spring2026!
# user2: Spring2026!
# user3: Spring2026!
# ... (wait for lockout timer to reset) ...
# user1: Company123!
# user2: Company123!
# user3: Company123!
This technique is highly effective against organizations with predictable password policies. If a company requires passwords to be changed quarterly and enforces a minimum of 8 characters with uppercase, lowercase, and a number, many employees will choose patterns like "Season+Year+Symbol" (e.g., "Summer2026!").
Why It Evades Detection
- Avoids account lockout -- only 1-2 attempts per account stay below most lockout thresholds
- Low and slow -- attackers space attempts over hours or days
- Distributed sources -- attacks may come from multiple IP addresses or cloud services
- Legitimate-looking traffic -- a single failed login per user does not look suspicious in most logs
Rainbow Tables and Offline Attacks
When attackers obtain a database of password hashes (through SQL injection, a compromised backup, or insider access), they can attack the hashes offline with no rate limiting and no detection.
What Are Rainbow Tables
A rainbow table is a precomputed lookup table that maps hash values back to their original plaintext passwords. Instead of computing hashes in real time, the attacker simply looks up the hash and retrieves the password instantly.
# Example: how a rainbow table lookup works
Hash found in database: 5f4dcc3b5aa765d61d8327deb882cf99
Rainbow table lookup: 5f4dcc3b5aa765d61d8327deb882cf99 -> "password"
# The hash is the MD5 of "password"
# No computation needed -- just a table lookup
Why Salting Defeats Rainbow Tables
A salt is a random value added to each password before hashing. Even if two users have the same password, their hashes will be different because they have different salts. This makes precomputed rainbow tables useless.
# Without salt (vulnerable to rainbow tables):
MD5("password") = 5f4dcc3b5aa765d61d8327deb882cf99 (same for every user)
# With salt (rainbow tables are useless):
salt1 = "x7Kp2"
MD5("x7Kp2" + "password") = a1b2c3d4e5f6... (unique per user)
salt2 = "Qm9Rv"
MD5("Qm9Rv" + "password") = f6e5d4c3b2a1... (different hash, same password)
Algorithms like bcrypt, scrypt, and Argon2 are specifically designed for password storage. They include built-in salting, are intentionally slow (to make brute force impractical), and have configurable "work factors" that can be increased as hardware gets faster. MD5 and SHA-1 should never be used for password storage -- they are designed for speed, which is the opposite of what you want for passwords.
Online vs. Offline Attacks
- Online attacks -- the attacker sends login attempts to a live service (SSH, web login, API). These are limited by network speed, rate limiting, account lockout, and detection systems
- Offline attacks -- the attacker has obtained password hashes and cracks them on their own hardware. There is no rate limiting, no lockout, and no detection. The only defense is strong hashing algorithms and long, complex passwords
Common Password Attack Tools
Understanding the tools attackers use helps defenders build appropriate countermeasures. These same tools are used by authorized penetration testers to evaluate password policies.
Hashcat -- GPU-Accelerated Cracking
# Crack MD5 hashes using a wordlist
hashcat -m 0 -a 0 hashes.txt /usr/share/wordlists/rockyou.txt
# Crack bcrypt hashes (mode 3200) with rules
hashcat -m 3200 -a 0 hashes.txt wordlist.txt -r rules/best64.rule
# Benchmark your GPU's cracking speed for various hash types
hashcat -b
John the Ripper -- Versatile Cracker
# Auto-detect hash type and crack
john hashes.txt
# Use a specific wordlist
john --wordlist=/usr/share/wordlists/rockyou.txt hashes.txt
# Show cracked passwords
john --show hashes.txt
# Extract hashes from /etc/shadow (authorized systems only)
sudo unshadow /etc/passwd /etc/shadow > unshadowed.txt
john unshadowed.txt
Hydra -- Online Attack Tool
# SSH login attack with a wordlist
hydra -l admin -P passwords.txt 192.168.1.50 ssh
# FTP login with username and password lists
hydra -L users.txt -P passwords.txt 192.168.1.50 ftp
# HTTP Basic Auth
hydra -l admin -P passwords.txt 192.168.1.50 http-get /admin/
Defensive Measures
Understanding attack methods is only valuable if you use that knowledge to build strong defenses. Here are the most effective countermeasures against password attacks.
Strong Password Policies
- Minimum 12 characters -- length is the single most effective defense against brute force
- Encourage passphrases -- "purple-elephant-runs-quickly" is stronger and more memorable than "P@ssw0rd!"
- Check against breach databases -- reject passwords that appear in known breach compilations (NIST SP 800-63B recommendation)
- Remove arbitrary complexity rules -- requiring uppercase/lowercase/symbol/number leads to predictable patterns like "Password1!"
Multi-Factor Authentication (MFA)
MFA requires a second factor beyond the password -- something you have (a phone, hardware key) or something you are (fingerprint, face). Even if an attacker obtains the password, they cannot log in without the second factor.
- TOTP apps -- Google Authenticator, Authy, or similar apps generate time-based codes
- Hardware security keys -- YubiKey, Google Titan -- the strongest option, resistant to phishing
- Push notifications -- approve login from your phone (watch out for MFA fatigue attacks)
- SMS codes -- better than nothing, but vulnerable to SIM swapping; avoid if possible
Microsoft reports that MFA blocks 99.9% of automated account compromise attempts. Even if every password in your organization is weak, MFA provides a strong second line of defense. It is the single most impactful security measure you can deploy.
Account Lockout and Rate Limiting
- Progressive delays -- increase the wait time after each failed attempt (1s, 2s, 4s, 8s...)
- Temporary lockout -- lock the account for 15-30 minutes after 5-10 failed attempts
- CAPTCHA after failures -- present a CAPTCHA challenge after 3 failed login attempts
- IP-based rate limiting -- limit login attempts per IP address to counter spraying attacks
- Alerting -- notify users and admins of unusual login activity
Proper Password Storage
# WRONG: storing passwords in plaintext or with fast hashes
MD5("password") -- cracked instantly
SHA256("password") -- cracked in seconds with GPU
# RIGHT: using purpose-built password hashing algorithms
bcrypt(cost=12, "password") -- ~250ms per hash attempt
argon2id(t=3, m=64MB, "password") -- memory-hard, GPU-resistant
- Use bcrypt, scrypt, or Argon2id -- never MD5, SHA-1, or SHA-256 for passwords
- Unique salt per password -- these algorithms handle this automatically
- Tune the work factor -- aim for ~250ms per hash on your server hardware
- Use a password manager -- Bitwarden, KeePassXC, or 1Password generate and store unique passwords for every account
Now Do It Yourself: Crack Passwords (Your Own) in Five Steps
The fastest way to respect password security is to break it once, safely, with your own data. You will build a small “stolen” database of unsalted hashes, crack most of it against an eight-word list, see why one entry survives, and then rebuild it the right way — salted and slow — so the same attack falls apart. Every block of output below was produced by running the code.
Go: a terminal in a folder you can write to.
Do: save this as crack.py. It stores four users’
passwords as plain MD5 hashes — unsalted and fast, which is exactly the
mistake real breaches are full of. Three passwords are common; dave’s is strong.
import hashlib
def md5(s): return hashlib.md5(s.encode()).hexdigest()
stolen = {
"alice": md5("password"),
"bob": md5("123456"),
"carol": md5("qwerty"),
"dave": md5("Tq7x2Kx_wZ9r"), # a strong, unique password
}
for user, h in stolen.items():
print(" %-6s %s" % (user, h))
You should see: four 32-character hex strings — one per user. To an attacker who steals this table, these are the passwords, if the hashing was weak.
If not: if you get a NameError, check the
def md5 line is above where it is used. Nothing here connects to a network; it is
all local strings.
Go: the same file.
Do: add a small wordlist and crack every hash you can by pre-hashing each guess and matching. Real attackers use lists of millions; eight is enough to make the point.
wordlist = ["password","123456","qwerty","letmein",
"monkey","dragon","football","sunshine"]
table = {md5(w): w for w in wordlist} # hash -> word, computed once
print("cracking unsalted MD5 against an 8-word list:")
for user, h in stolen.items():
print(" %-6s %s" % (user, table.get(h, "*** not cracked ***")))
You should see: three of the four fall instantly, and the strong one stands:
cracking unsalted MD5 against an 8-word list:
alice password
bob 123456
carol qwerty
dave *** not cracked ***
That is a password breach in six lines. The only thing that saved dave was that the password itself was not in the list — not the hashing, which gave up the other three for free.
If not: if everything says “not cracked”, your
table is hashing something different from stolen — both must
use the same md5 function defined in step 1.
Go: look again at the line table = {md5(w): w for w in
wordlist}.
Do: notice that the attacker hashes each candidate word once and then every stolen hash is an instant dictionary lookup. Add a counter to prove it.
print("hashes computed:", len(wordlist), "-- but", len(stolen), "accounts were checked")
You should see: hashes computed: 8 -- but 4 accounts were
checked. With unsalted hashes the work does not grow with the number of victims: crack
one list, unlock everyone who used any of those passwords, across every unsalted database in
the world. This is what precomputed “rainbow tables” industrialise.
If not: the exact numbers depend on your list and user count; the point is that hashes computed stays tied to the wordlist, never to how many accounts you attack.
Go: a fresh file, defend.py.
Do: store the same weak password password, but with a
per-user random salt and scrypt, a deliberately slow hash. Then run the same
dictionary against it.
import hashlib, time
salt = b"per-user-random-salt" # real code uses os.urandom(16) per user
stored = hashlib.scrypt(b"password", salt=salt, n=16384, r=8, p=1)
wordlist = ["password","123456","qwerty","letmein","monkey","dragon","football","sunshine"]
t0 = time.time()
hit = next((w for w in wordlist
if hashlib.scrypt(w.encode(), salt=salt, n=16384, r=8, p=1) == stored), None)
print("cracked:", hit, "in %.2fs for 8 guesses" % (time.time()-t0))
You should see: the weak password is still found — but slowly, and only for this one salt:
cracked: password in 0.04s for 8 guesses
Eight guesses were cheap; a real 14-million-word list at this per-guess cost is hours, not milliseconds — and because every user has a different salt, the attacker must redo all of it separately for each account. Salt kills precomputed tables; a slow hash kills brute force. Weak passwords still lose, which is why the two layers together — strong passwords and salted slow hashing — are the standard.
If not: the seconds will differ on your machine (faster or slower CPU);
the ratio to instant MD5 is the lesson, not the exact number. If scrypt raises a
memory error, lower n to 4096.
Go: a terminal, with John the Ripper installed
(sudo apt install john).
Do: point it at a hash file you own. John does at industrial speed exactly
what your Python did — wordlists, rules that mutate words
(password→P@ssw0rd), and brute force.
john --wordlist=/usr/share/wordlists/rockyou.txt myhashes.txt
You should see: John print any weak passwords it recovers, then
john --show myhashes.txt lists them. The defensive takeaways are the whole point:
use bcrypt, scrypt, or Argon2 with a per-user salt (never MD5/SHA-1), enforce
long unique passwords (a manager makes this painless — see the Bitwarden
tutorial), and add rate limiting and MFA so online guessing is throttled
regardless of the hash.
If not: no rockyou.txt? It ships with Kali and is a download
elsewhere; any wordlist file works. If John finds nothing, your test passwords were strong
— which is exactly the outcome you want for real accounts.
Without scrolling up: two databases both store the password password. One uses
unsalted MD5, the other uses per-user salted scrypt. An attacker steals both. What is
different about how long each takes to crack, and why? Answer: the MD5 one falls
instantly — the hash of password is precomputed and identical for every
user, so a single table lookup cracks it and everyone else who used it. The scrypt one must be
attacked one salt at a time, hashing every guess slowly for each user separately, turning
milliseconds into hours. Same weak password, but salting kills the shared precomputation and
the slow hash kills the speed.
Now do it without the page: add a fifth user to your step-1
stolen dict whose password you generate with
python3 -c "import secrets,string; print(''.join(secrets.choice(string.ascii_letters+string.digits) for _ in range(16)))",
then run the step-2 attack again and confirm that user joins dave in *** not cracked
***. You have just proved to yourself that password length and randomness, not clever
hashing alone, is what defeats a dictionary.
Summary
In this tutorial, you learned about the major categories of password attacks and their defenses:
- Brute force -- trying every possible combination; defeated by password length and rate limiting
- Dictionary attacks -- using wordlists and mutation rules; defeated by avoiding common passwords
- Credential stuffing -- reusing stolen credentials across services; defeated by unique passwords per site
- Password spraying -- testing common passwords against many accounts; defeated by banning common passwords and MFA
- Rainbow tables -- precomputed hash lookups; defeated by salted, slow hashing algorithms
- Defensive measures -- strong password policies, MFA (especially hardware keys), account lockout, rate limiting, and proper hash storage
No single measure stops all password attacks. The strongest defense combines long unique passwords (via a password manager), multi-factor authentication, rate limiting and lockout policies, proper password hashing (bcrypt/Argon2), and monitoring for anomalous login patterns. Implement all of them.