What would you like to learn?

Try PowerShell, networks, XSS, or Step 138

Browse the full curriculum →

Penetration testing

Step 123. Password Attack 2: John the Ripper Offline Cracking — Stolen Hashes Break in Silence

Step 123Estimated practice · 3 hours

Level 2 — Introduction to Security and Attack Skill Basics | Difficulty ★★★☆☆ | Estimated time: 3 hours

Prerequisites: You’ve completed Step 122. You know the principle and limits of online brute force (slow, recorded, locked). You can use basic Python syntax.

  • What you need: Kali (or a Linux lab + Python 3). If you have MS2’s shadow file captured in Step 117, all the better
  • Caution: ⚠️ All exercises in this chapter are for your own lab and legal platforms only. Applying them to unauthorized systems is a crime.

Step 122’s online attack was 18ms per attempt, and every attempt was logged. But what if an attacker steals the server’s entire password store (/etc/shadow)? The answer is today’s topic. A hash file can be cracked on your own computer — no logs, no lockouts — millions of times per second. Today we measure with Python how Linux stores passwords (hashes and salts), measure for ourselves the overwhelming speed difference of offline cracking, and finish with how to use the industry-standard tool John the Ripper.


1. Learning Objectives

By the end of this chapter, you will be able to:

  • Read the format of one /etc/shadow line (account, algorithm, salt, hash)
  • Explain that a hash is a transformation that "can’t be reversed but can be guessed"
  • Simulate an offline dictionary attack with Python hashlib and measure its speed
  • Demonstrate that a salt turns the same password into different hashes, and state its defensive meaning
  • Perform john’s basic usage (cracking, –show, unshadow) in the lab

2. Background Knowledge — Today’s Tools and Concepts

Today’s Tools at a Glance

Category Details
Language/environment Python 3 standard library (hashlib, plus crypt on Linux) + Kali’s john
Today’s commands john --wordlist=... file, john --show file, unshadow passwd shadow
Concepts needed One-way hashes, /etc/shadow format, salts, dictionary attacks, offline speed
Today’s artifact An offline-cracking simulator + a speed-measurement note + a john cracking record

2-1. Hashes — Can’t Be Reversed, But Can Be Guessed

Linux doesn’t store passwords in plaintext. It stores only the result of passing the password through a hash function (a one-way transformation). Hashes can’t be reversed — sunshine produces a941a4c4..., but there’s no formula that computes sunshine back from a941a4c4....

Yet cracking is not reversal — it’s testing. Hash candidates one by one and compare with the stored hash. If they match, that candidate is the answer. Unlike Step 122’s "submission," which went through a server, this comparison happens entirely inside my own computer — and that is what makes every difference of the offline world.

2-2. Reading /etc/shadow — Anatomy of One Line

The front part of a real file (measured 2026-09-09 on a Linux lab):

root:*:20494:0:99999:7:::
daemon:*:20494:0:99999:7:::
bin:*:20494:0:99999:7:::

Of the fields split by colons (:), the first is the account and the second is the hash. * means "this account has password login locked." An account with a hash looks like this:

msfadmin:$1$XN10Zj2c$Rt/zzCW3mLtUWA.ihZjA5/:...
          ↑    ↑           ↑
       algorithm  salt      hash body

The algorithm number matters most. $1$ is MD5-based (old), $5$ is SHA-256, $6$ is SHA-512, and $y$ is yescrypt (the latest). This one number decides "how hard this storage method is to crack."

2-3. Salts — Same Password, Different Hash

A salt is a random additive mixed into the password before hashing. Even for the same sunshine, a different salt makes a completely different hash. Why mix it in? Without salts, every sunshine in the world has the same hash, so an attacker can flip everything in an instant with a single precomputed table (a rainbow table) of "common password → hash." With salts, every hash has a different salt, precomputation becomes useless, and each user must be dictionary-attacked separately. We’ll see this with our own eyes in 3-4.

2-4. Online vs. Offline — The Worldview of Speed

In Step 122’s measurement, online was 18.3ms per attempt (about 55 tries/second). And offline? We’ll measure it ourselves in 3-3, but to state the conclusion first: about 2 million tries per second on the same computer — roughly 37,000 times faster. No logs, no lockouts, no alerts. That’s why a shadow-file leak is treated as an incident equivalent to "the passwords themselves were stolen," and why shadow is locked so only root can read it — the reason an attack chain runs "get a shell → escalate to root → steal shadow."


3. Follow Along

3-1. Making Hashes — The Moment Plaintext Disappears

Input (hash_basics.py)

import hashlib

print("md5   :", hashlib.md5(b"sunshine").hexdigest())
print("sha256:", hashlib.sha256(b"sunshine").hexdigest())

Output (measured 2026-09-09):

md5   : 0571749e2ac330a7455809c6b0e7af90
sha256: a941a4c4fd0c01cddef61b8be963bf4c1e2b0811c037ce3f1835fddf6ef6c223

How to read it: eight letters became 32/64 hexadecimal digits. Change even one letter of the input and the output flips completely (compare sunshine and sunshinf). And there’s no formula to get the original word back from this output — that’s why it’s "one-way."

Note: on Windows Python, calling the crypt module, which makes Linux-style hashes, raises an ImportError saying The crypt module is not supported on Windows (measured 2026-09-09). This module works only on Linux (including Kali) — because it imitates the operating system’s password storage scheme.

3-2. Making a shadow-Format Hash — crypt on Linux

On Linux (Kali or an Ubuntu lab), Python can make hashes in exactly the stored format.

Input (shadow_hash.py — run on Linux)

import crypt
h1 = crypt.crypt("sunshine", "$6$rounds=5000$ab12cd34")
h2 = crypt.crypt("sunshine", "$6$rounds=5000$zz99yy88")
print("salt A:", h1)
print("salt B:", h2)
print("same?", h1 == h2)

Output (measured 2026-09-09):

salt A: $6$rounds=5000$ab12cd34$qTWpyN0hAgo1XRCmSHtpIAdpn.6YmJn9iPpRgqFgM.HlUFQytBvf77ZfBW80oni6s9M6dg1Ag0cbjV1EiyQht0
salt B: $6$rounds=5000$zz99yy88$Gu9dYCSLbb32dqfm/nwv49FBcszKt0QXhG6eAR4UUZGnN1asrOvET/4lXJ.JmqyvgOqcSyLBMQa9dwZ8OsRtj0
same? False

How to read it: $6$ = SHA-512 based, rounds=5000 = deliberately compute 5,000 iterations (deliberately slow), followed by the salt and the hash body. It’s exactly the shadow format you saw in 2-2. Same password, but with only the salt changed, the hash came out completely different — this is the visual evidence of the salt.

3-3. Offline Dictionary Attack — The Simulator and Speed Measurement

Now let’s become "the attacker who stole the hash file." The target hash is 3-1’s sha256 (the one for sunshine).

Input (offline_crack.py)

import hashlib, time

# Simulated 'stolen hash file'
target = "a941a4c4fd0c01cddef61b8be963bf4c1e2b0811c037ce3f1835fddf6ef6c223"

words = [l.strip() for l in open("wordlist.txt", encoding="utf-8") if l.strip()]
start = time.time()
for i, w in enumerate(words, 1):
    if hashlib.sha256(w.encode()).hexdigest() == target:
        print(f"[+] Crack success: admin / {w} (attempt {i})")
        break

# Raw attempt-speed measurement — offline's true terror
start = time.time()
for n in range(1_000_000):
    hashlib.sha256(b"candidate").hexdigest()
el = time.time() - start
print(f"offline sha256 speed: {1_000_000/el:,.0f} attempts/sec (1 million in {el:.2f}s)")

Output (measured 2026-09-09):

[+] Crack success: admin / sunshine (attempt 17)
offline sha256 speed: 2,036,837 attempts/sec (1 million in 0.49s)

How to read it: compare two numbers. Step 122’s online attack was 18.3ms per attempt (about 55/second); now it’s about 2 million per second. A difference of about 37,000 times. Running all of rockyou (14.34 million entries) at this speed takes about 7 seconds — a job that took 73 hours online. And this is the story of an unsalted "fast hash"; professional tools using GPUs are tens to hundreds of times faster still.

Why: the speed of offline cracking is itself the answer to "why is a hash-file leak a major incident?" At the same time, thinking in reverse — the reason defenders make hashes deliberately slow (rounds=5000, yescrypt) also comes out of this number.

3-4. The Salt’s Defensive Effect — Seeing It with Your Own Eyes

Input (salt_demo.py)

import hashlib

print("no salt  :", hashlib.sha256(b"sunshine").hexdigest()[:16], "...")
print("salt ab12:", hashlib.sha256(b"ab12" + b"sunshine").hexdigest()[:16], "...")
print("salt zz99:", hashlib.sha256(b"zz99" + b"sunshine").hexdigest()[:16], "...")

Output (measured 2026-09-09):

no salt  : a941a4c4fd0c01cd ...
salt ab12: 7df0d38574964494 ...
salt zz99: 153d1b122597ac05 ...

How to read it: all three hashes are different. Even if an attacker precomputed "the hash of sunshine," that table is useless against a store with salts attached. Since each user has a different salt, cracking a million people’s hashes requires a million separate dictionary attacks. A salt is a device that demotes "mass cracking" to "individual cracking."

3-5. John the Ripper — The Standard Procedure (lab practice, screen examples)

Work in your own Kali. The target is MS2’s shadow, captured in Step 117 (if you don’t have it, run sudo cat /etc/shadow > /tmp/shadow.txt from an MS2 shell and copy it to Kali — recall Step 119’s nc file transfer). The outputs are screen examples.

The standard procedure: combine passwd and shadow with unshadow

unshadow passwd.txt shadow.txt > combined.txt

Cracking

john --wordlist=/usr/share/wordlists/rockyou.txt combined.txt
Loaded 7 password hashes with 7 different salts (md5crypt ...)
Press 'q' or Ctrl-C to abort
msfadmin         (msfadmin)

Viewing results again

john --show combined.txt
msfadmin:msfadmin:...
1 password hash cracked, 6 remaining

How to read it: john automatically identifies the hash format (md5crypt) and hashes each wordlist candidate together with that account’s salt for comparison. Cracked results are saved in a .pot file, so you can view them again anytime with --show. Being $1$ (MD5), it’s on the easy side to crack; with $y$ (yescrypt) it would take far longer — this is where you feel that the algorithm number is defensive strength.

Stuck tip: only root can read shadow — that’s why the real-world chain is "get a shell → escalate privileges → steal shadow." If cracking runs long, record even partial cracks as success. The goal isn’t cracking everything — a penetration test’s goal is "proving that weak accounts exist."


4. Missions & Exercises

Mission — An Offline-Cracking Experiment Note

  1. Complete the simulator from 3-3 and record your computer’s offline speed (attempts/sec)
  2. Compare it with Step 122’s online speed (ms per attempt) and calculate "how many times faster"
  3. Confirm the three hashes differ in the salt experiment from 3-4, and write "the attack the salt blocks" in one sentence
  4. Find algorithm numbers like $1$ and $6$ in MS2’s shadow and annotate what scheme each one is
  5. Crack at least one hash with john and copy the john --show result into your notes

Exercises

Exercise 1. A hash "can’t be reversed," yet cracking is possible — explain why.

Exercise 2. In /etc/shadow‘s msfadmin:$1$XN10Zj2c$Rt/zz..., state what $1$, XN10Zj2c, and the long string after them each are.

Exercise 3. Explain the structural reason for the speed difference between online and offline attacks in terms of "the path a single attempt travels."

Exercise 4. In a world without salts, what shortcut (precomputed table) can an attacker use, and how does a salt neutralize it?


Answers & completion criteria · expand/collapse

5. Model Answers & Completion Criteria

Mission Model Answer

Speed comparison example (based on the 2026-09-09 measurement — your values will differ):

Online (Step 122): 18.3ms per attempt → about 55/sec
Offline (3-3):     2,036,837/sec
Ratio: about 37,000x
Conclusion: even with the same wordlist, offline is 30,000+ times faster, with no logs or lockouts.
      → A hash-file leak = an incident comparable to a password leak.

Example one-sentence answer for #3: "A salt makes precomputed tables (rainbow tables) useless, and with a million users it forces a million separate dictionary attacks."

Example algorithm annotations: $1$ MD5-based (old, cracks fast), $5$ SHA-256, $6$ SHA-512, $y$ yescrypt (latest, most robust).

How to verify: ① is there a measured speed and ratio calculation? ② are the three hashes in the salt experiment actually different? ③ did you read the shadow’s algorithm numbers? ④ did you copy the john –show result into your notes?

Exercise Answers

Answer 1. The hash itself can’t be inverted, but cracking is not inversion — it’s comparison: hash a candidate and compare it with the stored hash. Because the candidate space of passwords is bounded by the size of human memory, "hashing them all" is practical.

Answer 2. $1$ is the hash algorithm number (MD5-based storage), XN10Zj2c is the salt (a random additive unique to this account), and what follows is the hash body computed with the salt mixed in. These three chunks are separated by $ and packed into one field.

Answer 3. One online attempt goes through a network round trip + the server’s authentication processing + a response (measured 18.3ms), while one offline attempt ends with a hash computation + string comparison inside my computer (measured about 2 million/second). The difference of whether there’s a counterpart to wait for creates a speed difference of 30,000 times or more.

Answer 4. Without salts, the same password always has the same hash, so a giant precomputed table (rainbow table) of "common password → hash" can flip an entire store with a single lookup. When each user’s salt differs, each user’s hash differs too, so precomputation is impossible and every hash needs a fresh dictionary attack.

Completion Criteria Checklist

  • [ ] I can read the fields of one /etc/shadow line (account, algorithm, salt, hash)
  • [ ] I know the relationship between algorithm numbers ($1$/$5$/$6$/$y$) and robustness
  • [ ] I simulated an offline dictionary attack with hashlib and measured its speed
  • [ ] I calculated the offline-to-online speed ratio
  • [ ] I can explain the salt’s defensive effect with three measured hashes
  • [ ] I cracked at least one hash with john and confirmed it with –show
  • [ ] I can state why stealing shadow is dangerous (privilege, speed, no logs)

6. Common Pitfalls & Fixes

Wall 1. crypt doesn’t work on Windows

Symptom (measured message, 2026-09-09): ImportError: The crypt module is not supported on Windows
Cause: the crypt module is Linux-only — it uses Linux’s password storage scheme.
Fix: run it on Kali (Linux). On Windows, the hashlib experiments in 3-1, 3-3, and 3-4 alone confirm all the principles.

Wall 2. john says "No password hashes loaded"

Symptom: you ran john but it can’t read any hashes.
Cause: the file format is mixed up, or — commonly — you fed shadow directly and the field parsing got tangled.
Fix: the standard play is combining first with unshadow passwd.txt shadow.txt > combined.txt, then feeding that to john. Also check that the account order matches between passwd and shadow.

Wall 3. cat-ing shadow gives Permission denied

Symptom: cat /etc/shadow is refused.
Cause: that’s normal. Only root reads shadow — this permission setting is itself the scene of today’s "protecting the hash file" lesson.
Fix: in the lab (MS2), read it with sudo or a root shell. This refusal is precisely the answer to "why does the attack chain need privilege escalation?"

Wall 4. Cracking won’t finish for hours

Symptom: john keeps running and has cracked nothing.
Cause: the answer may not be in the wordlist, or a robust hash scheme ($6$, $y$) simply takes a long time.
Fix: stopping with Ctrl+C still saves progress. If anything cracked, check with --show and record it in your notes. "Couldn’t crack everything" is also a valid result showing that store’s defensive strength.

Wall 5. It’s a salt experiment but the hashes come out the same

Symptom: you changed the salt but the hash is identical.
Cause: check whether the order you feed the hash function is consistently salt + password. Most cases are accidentally leaving the salt outside the hash or copying the same salt twice.
Fix: read the code to confirm the salt went inside the input bytes, like hashlib.sha256(b"ab12" + pw), and compare with the measured output in 3-4.


7. Summary

Today’s Concepts

Concept One-line explanation
Hash (one-way transformation) A transformation that can’t be reversed but can be guessed by comparing candidates
/etc/shadow Linux’s password store — account:$algorithm$salt$hash:…
Algorithm number $1$ MD5 (old) → $6$ SHA-512 → $y$ yescrypt (latest, robust)
Salt A per-account random additive — neutralizes precomputed tables
Offline cracking An attack that compares stolen hashes on your own computer, unlogged and unlimited
Rainbow table A precomputed table that flips unsalted hashes with a single lookup

Today’s Commands & Functions

Command/function What it does
hashlib.sha256(bytes).hexdigest() Compute a hash in Python (for experiments)
crypt.crypt(password, "$6$rounds=...$salt") Make a shadow-format hash (Linux)
unshadow passwd.txt shadow.txt > combined.txt Combine two files for cracking
john --wordlist=rockyou.txt combined.txt Run offline cracking
john --show combined.txt View cracked results again
cat /etc/shadow (root required) Read the hash store — a privilege experience

An Instinct More Important Than Commands

Remember today’s single number: 2 million per second. The modern rule that password storage must use "slow, salted hashes" is entirely a defense against this number. Why defenders use yescrypt and iteration counts, why attackers aim to steal shadow — two faces of the same number.

And remember the chain: get a shell → escalate privileges → steal shadow → crack offline → expand to other accounts and other servers. The path by which a breach grows from "one machine’s intrusion" to "total takeover" is inside this one line. What you can do for defense at your current stage is simple — keep systems on modern hash schemes, use long passwords not in dictionaries, and watch for files like shadow leaving the building.


Once every box is checked, Step 123 is complete.

ONE STEP FURTHER

Finished this lesson?

Check the completion criteria, then mark your progress.

Something wrong with this page or a link? Let us know.
Next