How to Create Custom Password Wordlists with CeWL, Crunch, and CUPP

Deepak Prasad
Tested on Kali GNU/Linux Rolling 2026.2 (kali-rolling)
Package cewl 6.2.1-1
crunch 3.6-3.1
cupp 0.0+20190501.git986658-6
Lab environment Pentest lab
Cert prep CompTIA PenTest+ PT0-003 — password attacks and offline cracking prep
Privilege Normal user for generation; sudo for apt install
Scope Choose and build custom password wordlists on Kali with CeWL (site words), CUPP (lab profile seeds), and Crunch (known patterns), then clean, merge, and feed John or hashcat. Does not cover live login brute force, GPU tuning, or crawling third-party sites without authorization.
Related guides SSH brute force attack
Short on time?Beta Get the gist of this 13-minute read in seconds, then ask follow-up questions

A custom wordlist is a smaller, smarter candidate set than dumping millions of random strings at a hash or login. CeWL, CUPP, and Crunch each solve a different input problem. I ran the commands below on Kali Rolling under /tmp/wordlist-lab against a local HTML site and invented lab identity, then fed short results into John and hashcat.

IMPORTANT
Build and use wordlists only for systems you own or are authorized to test in your rules of engagement. Spider only in-scope sites. Do not profile real people or crawl random internet hosts for practice.

Choose the right wordlist strategy

Pick the generator from what you already know about the target, not from which tool has the most flags.

Tool Best when you have Output style
CeWL An authorized website or portal with org-specific vocabulary Seed words scraped from page text
CUPP Structured profile-style facts from an authorized audit (invented lab identity here) Many mutations of those seeds
Crunch Exact length, charset, or positional pattern Every string that matches the pattern
Existing lists (rockyou, SecLists) Common passwords with no better lead Broad baseline
Rules / masks (John, hashcat) Seeds or patterns that expand on the fly Candidates without storing every line
text
What do you know?
        │
        ├─ site / product language ──────────────► CeWL → clean seeds
        ├─ person / org profile facts (authorized) ► CUPP → profile variants
        ├─ exact shape (prefix, digits, charset) ─► Crunch or hashcat mask
        └─ only “common password” guess ─────────► rockyou / SecLists + rules

Rules and masks often beat writing millions of Crunch lines to disk. Generate a file when you need to share, prioritize, or feed a tool that only accepts a list. Expand dynamically when the cracker can express the pattern itself.


Why bigger wordlists are not automatically better

Candidate ordering and relevance matter more than raw line count.

Huge unconstrained lists cost you:

  • Disk and I/O while the cracker reads aaaa, aaab, …
  • Wasted early guesses that never match policy
  • False confidence that “we tried everything”

A short list of target words plus rules or a tight mask usually finishes meaningful work sooner. Crunch is honest about this: a four-character abc run starts with aaaa-style noise, and an eight-character full alphabet run advertises well over 1 TB of output before it is useful.


Install the generators on Kali

Refresh the three packages if any binary is missing. Use apt on Kali:

bash
sudo apt update

Install or refresh the three generators:

bash
sudo apt install -y cewl crunch cupp

Confirm versions on this image:

bash
cewl --help 2>&1 | head -1
output
CeWL 6.2.1 (More Fixes) Robin Wood (robin@digi.ninja) (https://digi.ninja/)

Check Crunch the same way:

bash
crunch 2>&1 | head -1
output
crunch version 3.6

Ask dpkg for the CUPP package version, because cupp -v prints project credits rather than a short NEVRA:

bash
dpkg-query -W cupp
output
cupp	0.0+20190501.git986658-6

CeWL 6.2.x is still maintained upstream. Treat it as a current Kali tool, not an abandoned scraper.

Create a scratch directory for every file this lab writes:

bash
mkdir -p /tmp/wordlist-lab/{site,out} && cd /tmp/wordlist-lab

Create website-derived wordlists with CeWL

CeWL spiders a URL and extracts words you can feed to a cracker. For this lab I served a tiny Northwind portal on localhost so the crawl stays authorized and reproducible. Point the same flags at an in-scope client site when you have written permission.

Write two HTML pages with org-specific terms:

bash
cat > /tmp/wordlist-lab/site/index.html <<'EOF'
<!DOCTYPE html>
<html>
<head><title>Northwind Lab Portal</title></head>
<body>
<h1>Welcome to Northwind Trading</h1>
<p>Northwind Trading ships Contoso widgets from Seattle warehouses.</p>
<p>Popular projects: Glacier, Aurora, Cascade, Fjord.</p>
<a href="/about.html">About</a>
</body>
</html>
EOF

The second page lets the crawl demonstrate following an internal link at the selected depth:

bash
cat > /tmp/wordlist-lab/site/about.html <<'EOF'
<!DOCTYPE html>
<html><head><title>About Northwind</title></head>
<body>
<h2>About Northwind Trading</h2>
<p>Founded in Seattle. Brands include Contoso and Fabrikam.</p>
</body></html>
EOF

Start a one-off HTTP listener on that folder:

bash
python3 -m http.server 8765 --directory /tmp/wordlist-lab/site

Leave that terminal running, or background it, then crawl depth 1 and keep words of length 5 or more:

bash
cewl -d 1 -m 5 -w /tmp/wordlist-lab/out/cewl_raw.txt http://127.0.0.1:8765/
output
CeWL 6.2.1 (More Fixes) Robin Wood (robin@digi.ninja) (https://digi.ninja/)

CeWL prints the banner and writes the file quietly on success. Count the lines:

bash
wc -l /tmp/wordlist-lab/out/cewl_raw.txt
output
32 /tmp/wordlist-lab/out/cewl_raw.txt

Peek at the top of the list:

bash
head -15 /tmp/wordlist-lab/out/cewl_raw.txt
output
Northwind
Trading
Contoso
About
Fjord
Cascade
Aurora
Glacier
Seattle
words
lists
Password
Fabrikam
include
Brands

Useful seeds such as Northwind, Glacier, and Contoso sit next to junk like About and include. Do not pass this file straight into a long crack without cleaning.

Stay on-site unless scope explicitly allows otherwise. Omit -o / --offsite for normal engagements so CeWL does not follow external links.


Clean the CeWL output

Normalize case, drop short and non-alpha tokens, and remove filler words before you spend GPU time.

Lowercase during the crawl when you want a case-folded seed list. Keep the original mixed-case output as well if capitalization itself may be meaningful; otherwise let John or hashcat rules regenerate case variants later:

bash
cewl -d 1 -m 5 --lowercase -w /tmp/wordlist-lab/out/cewl_lower.txt http://127.0.0.1:8765/

Count the lowercase list:

bash
wc -l /tmp/wordlist-lab/out/cewl_lower.txt
output
30 /tmp/wordlist-lab/out/cewl_lower.txt

Filter to org-flavored seeds (length ≥ 5, letters only, drop common English filler):

bash
python3 <<'PY'
from pathlib import Path
noise = {
    'about','words','lists','include','brands','founded','welcome','ships',
    'widgets','projects','popular','access','contact','login','password',
    'internal','portal','shipping','logistics','warehouses',
}
raw = Path('/tmp/wordlist-lab/out/cewl_lower.txt').read_text().splitlines()
seen, out = set(), []
for w in raw:
    w = w.strip().lower()
    if len(w) < 5 or not w.isalpha() or w in noise:
        continue
    if w not in seen:
        seen.add(w)
        out.append(w)
Path('/tmp/wordlist-lab/out/cewl_clean.txt').write_text('\n'.join(out) + '\n')
print(f'raw_lower={len(raw)} cleaned={len(out)}')
print('\n'.join(out))
PY
output
raw_lower=30 cleaned=10
northwind
trading
contoso
fjord
cascade
aurora
glacier
seattle
fabrikam
helpdesk

Thirty lowercase tokens became ten seeds worth keeping. Manual review still matters: delete anything that is clearly UI chrome, then keep product and place names.

Stop the local web server when you finish the CeWL steps (Ctrl+C in that terminal, or kill the background PID).


Generate profile-oriented candidates with CUPP

CUPP builds mutations from structured answers: names, dates, company, optional keywords. Use an invented lab identity for practice. On a real engagement, only use profile data you are authorized to process.

Run interactive mode quietly:

bash
cd /tmp/wordlist-lab/out && cupp -i -q

Answer with a fake Northwind staff profile (empty fields are allowed where CUPP permits Enter). The prompts look like this:

text
> First Name: Alex
> Surname: Rivera
> Nickname: ARiv
> Birthdate (DDMMYYYY): 03121990
> Partners) name: Jordan
> Partners) nickname: Jo
> Partners) birthdate (DDMMYYYY): 15051992
> Child's name: Sam
> Child's nickname: Sammy
> Child's birthdate (DDMMYYYY):
> Pet's name: Mochi
> Company name: Northwind
> Do you want to add some key words about the victim? Y/[N]: y
> Please enter the words, separated by comma. [...]: Glacier,Cascade
> Do you want to add special chars at the end of words? Y/[N]: y
> Do you want to add some random numbers at the end of words? Y/[N]: y
> Leet mode? (i.e. leet = 1337) Y/[N]: n

When generation finishes, CUPP writes alex.txt in the current directory. On this lab run it reported sorting, removed duplicates, and saved about 29,000 words (wc -l counted 29381 lines).

Sample lines show how seeds expand into dates, company words, and suffixes:

bash
grep -E '^(Alex1990|Glacier|Northwind|Mochi)' /tmp/wordlist-lab/out/alex.txt | head -20
output
Alex1990
Alex199003
Alex199012
Glacier
Glacier!
Glacier!!
Glacier!!!

Nearly thirty thousand lines is still smaller than unconstrained Crunch, but it is already noisy. Review and prioritize the generated candidates instead of assuming CUPP's output order reflects password likelihood. Merge CUPP output with CeWL seeds and then deduplicate when you want a deliberate attack order.


Use Crunch for known patterns

Crunch enumerates every candidate that matches a length and charset or a -t template. Use it when the shape is known. Do not use it as a substitute for “try everything.”

Pattern placeholders:

Symbol Character class
@ Lowercase
, Uppercase
% Digits
^ Symbols

Generate glacier plus two digits (glacier00 … glacier99):

bash
crunch 9 9 -t glacier%% -o /tmp/wordlist-lab/out/crunch_glacier.txt
output
Crunch will now generate the following amount of data: 1000 bytes
0 MB
0 GB
0 TB
0 PB
Crunch will now generate the following number of lines: 100 

crunch: 100% completed generating output

Confirm the line count matches the banner:

bash
wc -l /tmp/wordlist-lab/out/crunch_glacier.txt
output
100 /tmp/wordlist-lab/out/crunch_glacier.txt

Inspect the first few candidates:

bash
head -5 /tmp/wordlist-lab/out/crunch_glacier.txt
output
glacier00
glacier01
glacier02
glacier03
glacier04

One hundred lines, fully relevant to the pattern. That is the Crunch sweet spot.

Contrast with a tiny unconstrained alphabet run so you see why forums complain about useless output. Limit length to four and charset to abc, and write the file:

bash
crunch 4 4 abc -o /tmp/wordlist-lab/out/crunch_abc4.txt
output
Crunch will now generate the following amount of data: 405 bytes
0 MB
0 GB
0 TB
0 PB
Crunch will now generate the following number of lines: 81 

Open the file and look at the first lines:

bash
head -6 /tmp/wordlist-lab/out/crunch_abc4.txt
output
aaaa
aaab
aaac
aaba
aabb
aabc

Those early aaaa lines are correct for the pattern and usually wrong for a real password policy.

Before you ever write an eight-character full-charset list, ask Crunch for the size banner and cancel before it fills the disk. Pipe the start of a default eight-character run into head so you only keep the estimate:

bash
crunch 8 8 2>&1 | head -12
output
Crunch will now generate the following amount of data: 1879443581184 bytes
1792377 MB
1750 GB
1 TB
0 PB
Crunch will now generate the following number of lines: 208827064576
aaaaaaaa
aaaaaaab
aaaaaaac
aaaaaaad
aaaaaaae
aaaaaaaf

Stop there. Express that space as a hashcat mask or John incremental/mask job instead of filling a disk.

Charset presets live in /usr/share/crunch/charset.lst (numeric, hex-lower, and others) when you want a named alphabet instead of typing digits by hand.


Know when not to generate a file

If hashcat can express the same idea as -a 3 mask, -a 6/-a 7 hybrid, or -r rules, you often should not materialize every string.

Same idea for John the Ripper: wordlist plus rules mutates seeds in memory. Keep a small seed file; let the cracker expand it.

Generate a file when:

  • You must hand the list to a teammate or another tool that only accepts a path
  • You want a prioritized order (org words first, then CUPP, then common passwords)
  • The pattern is tiny (hundreds of lines), as with glacier%%

Skip the file when:

  • The keyspace is huge but regular (?l?l?l?l?d?d)
  • You already have seeds and only need case or digit mangling
  • Crunch’s size banner shows gigabytes or worse

Merge, deduplicate, and prioritize lists

Build one ordered list: CeWL seeds, then pattern hits, then a tiny common-password stub, then unique CUPP leftovers.

bash
cd /tmp/wordlist-lab/out

Add a tiny common-password stub for the merge demo:

bash
printf 'password\nWelcome1\nadmin\n' > common_small.txt

Put the highest-value lines first, then append CUPP entries that are not already present:

bash
cat cewl_clean.txt crunch_glacier.txt common_small.txt > prioritized_head.txt

Collect CUPP lines that are not already in that head list. Compare exact lines so capitalized CUPP candidates such as Alex1990 and Glacier! stay intact; do not lowercase the leftovers into a seed file:

bash
comm -23 <(sort -u alex.txt) <(sort -u prioritized_head.txt) > cupp_rest.txt

Concatenate head and remainder while dropping blank lines and preserving first-seen order. Exact-line dedupe keeps distinct capitalizations as separate candidates:

bash
cat prioritized_head.txt cupp_rest.txt | awk 'NF && !seen[$0]++' > final_unique.txt

Compare the input piece sizes:

bash
wc -l cewl_clean.txt crunch_glacier.txt alex.txt
output
    10 cewl_clean.txt
   100 crunch_glacier.txt
 29381 alex.txt

final_unique.txt should stay well under a raw alex.txt dump when the prioritized head already covers org seeds and patterns. Confirm capitalized CUPP leftovers survived the merge:

bash
grep -E '^(Alex1990|Glacier!)' final_unique.txt | head -5

grep prints matching candidates when those exact lines remain in the merged list.

Read the prioritized head of the merged file:

bash
head -15 final_unique.txt
output
northwind
trading
contoso
fjord
cascade
aurora
glacier
seattle
fabrikam
helpdesk
glacier00
glacier01
glacier02
glacier03
glacier04

Org seeds and the glacier## block run first. Deduplicated CUPP content follows without pushing Northwind behind tens of thousands of Alex!! variants.


Feed the wordlist into John and hashcat

One offline example each. Full cracking workflows stay in the John and hashcat guides.

Crack an MD5 of glacier42 with the Crunch pattern file in John:

bash
echo -n 'glacier42' | md5sum | awk '{print $1}' | tee /tmp/wordlist-lab/out/demo.md5
output
4984ecf49050f5a309d1c2800760d964

Point John at the hundred-line Crunch file:

bash
john --format=raw-md5 --wordlist=/tmp/wordlist-lab/out/crunch_glacier.txt /tmp/wordlist-lab/out/demo.md5
output
Loaded 1 password hash (Raw-MD5 [MD5 256/256 AVX2 8x3])
glacier42        (?)     
1g 0:00:00:00 DONE (2026-09-03 14:05) 100.0g/s 10000p/s 10000c/s 10000C/s glacier00..glacier99
Session completed.

The hundred-line pattern file recovered glacier42 immediately. A multi-gigabyte Crunch dump was unnecessary.

Crack an MD5 of northwind with a three-word CeWL-style seed list in hashcat:

bash
echo -n 'northwind' | md5sum | awk '{print $1}' | tee /tmp/wordlist-lab/out/demo2.md5
output
34d3bab526de7c56e4775dab11aea797

Build a three-word seed list from the cleaned CeWL names:

bash
printf 'northwind\ncontoso\nglacier\n' > /tmp/wordlist-lab/out/tiny_feed.lst

Run a straight dictionary attack in hashcat against that MD5:

bash
hashcat -m 0 -a 0 /tmp/wordlist-lab/out/demo2.md5 /tmp/wordlist-lab/out/tiny_feed.lst -O -w 2 --logfile-disable
output
34d3bab526de7c56e4775dab11aea797:northwind

Session..........: hashcat
Status...........: Cracked
Recovered........: 1/1 (100.00%) Digests (total), 1/1 (100.00%) Digests (new)

Three relevant seeds beat a random million-line list for this hash.


Common mistakes

Mistake Why it hurts Prefer
Gigantic unconstrained Crunch keyspace TB-scale files of aaaa… noise Mask, hybrid, or tight -t pattern
Crawling unrelated or offsite pages Polluted seeds; possible scope violation In-scope host only; no --offsite unless approved
Feeding raw CeWL output UI words and filler waste early guesses Lowercase, length filter, manual noise removal
Skipping dedupe after merges Repeat candidates slow the cracker sort -u or order-preserving unique
No rules or mutations on good seeds Misses Glacier2024-style variants John rules / hashcat -r / hybrid
Storing candidates the cracker can generate Disk and I/O for no gain Mask or rules on a small seed file
Blind rockyou-only strategy Misses org vocabulary CeWL/CUPP seeds first, then common lists
Poor ordering Rare CUPP punctuation runs before org words Prioritize seeds and known patterns
Impossible lengths for the policy Guesses that can never match Match min/max length to the auth system

Summary

CeWL harvests site language, CUPP expands authorized profile-style seeds, and Crunch fills exact patterns. Start from what you know, keep lists small and ordered, and clean CeWL output before you burn cracking time.

Unconstrained Crunch keyspaces and raw scrape dumps look productive while they waste work. Prefer tight patterns, seed-plus-rules workflows, and masks when the candidate space is regular. When you do write a file, merge org seeds first, deduplicate, then hand the path to John or hashcat for a short validation run before a longer engagement job.


References


Frequently Asked Questions

When should I use CeWL instead of Crunch?

Use CeWL when an authorized website contains organization-specific words worth trying as password seeds. Use Crunch when you already know length, charset, or a positional pattern and need every matching string.

Is CUPP safe to use for practice?

Yes, if you invent a lab identity. Do not enter real personal data about people who did not consent, and do not build profile lists outside an authorized engagement.

Why does Crunch start with aaaa and similar strings?

Those lines are valid for the charset and length you asked for. Unconstrained alphabetic ranges enumerate in order, so low-value strings appear first. Tighten the pattern or use a mask instead of generating every combination.

Should I always save a wordlist to disk?

No. If hashcat or John can express the same candidates with masks, hybrids, or rules, generating every line to disk often wastes space and I/O. Save a file when you need prioritization, sharing, or a tool that only accepts a wordlist path.

How do I remove duplicates from merged wordlists?

Deduplicate exact lines with sort -u, or preserve first-seen order with awk '!seen[$0]++'. Only normalize case when you intentionally plan to regenerate capitalization variants with rules.

Finished reading?Beta Get the key takeaways in seconds, then ask follow-up questions
Kennedy Muthii

Information Security Analyst

Accomplished professional proficient in Python, ethical hacking, Linux, cybersecurity, and OSINT. With a track record including winning a national cybersecurity contest, launching a startup in Kenya, and holding a degree in information science, he is currently engaged in cutting-edge research in ethical hacking.

  • Python (programming language)
  • Certified Ethical Hacker
  • White Hat (Computer Security)
  • Linux
  • Penetration Testing