High-performance, multi-mode wordlist generator designed for Hashcat and John the Ripper.
Streams to STDOUT for live cracking or writes compressed/split files for large runs.
This README reflects all options implemented in
pwforge.py, including neural generation, hybrid/combo modes, entropy filtering, chunking, splitting, and estimation helpers.
- Python 3.8+ (tested on Linux, WSL, macOS, Windows)
- Standard library only (no extra pip deps required for non-neural modes)
- PyTorch (CPU or CUDA build)
Install from the official selector: https://pytorch.org/get-started/locally/ - PyTorch brings in
numpyas a dependency; you don’t need topip install numpyseparately unless you want to.
- PyTorch
- tqdm for progress bars
Install:
# Linux/macOS
python3 -m pip install torch tqdm
# Windows
python -m pip install torch tqdm
# (or python3 depending on your environment)python3 --version
python3 -c "import sys; print('python ok')"
# Neural users:
python3 -c "import torch, platform; print('torch', torch.__version__, 'cuda', torch.cuda.is_available())"# Random passwords (pw mode)
python3 pwforge.py --mode pw --count 1000000 | hashcat -a 0 -m 1000 hashes.ntlm
# Markov (requires training corpus)
python3 pwforge.py --mode markov --markov-train rockyou.txt --markov-order 3 --count 2000000 | hashcat -a 0 -m 0 hashes.md5
# PCFG (requires training corpus)
python3 pwforge.py --mode pcfg --pcfg-train rockyou.txt --count 1000000 | hashcat -a 0 -m 1000 hashes.ntlmpython3 pwforge.py --mode pw --count 1000000 | john --stdin --format=nt hashes/*John’s
--forkdoes not work with stdin. For multi-core JtR, generate a file:
python3 pwforge.py --mode walk --count 2000000 --out walks.txt --no-stdout
john --wordlist=walks.txt --format=nt --fork=16 hashes/*Neural generation uses a character-level LSTM checkpoint (.pt) and samples candidates, clamped to your --min / --max length.
# 1M neural candidates to hashcat
python3 pwforge.py --mode neural --model finetuned_model.pt --batch-size 512 --max-gen-len 32 --min 8 --max 20 --count 1000000 | hashcat -a 0 -m 1000 hashes.ntlmTo not have the output sent to the screen on windows
python3 ../pwforge/pwforge.py --mode neural --count 100000 --min 8 --max 16 --no-stdout --chunk 1000 --meter 0 --model ../neural/master_lstm.pt 2>NUL | hashcat.exe -a 0 -m 1000 -o master.txt ../hashes/ntjtr.txt -O --self-test-disable --bitmap-max 26 --username --session=hacktastic --keep-guessing --stdin-timeout 1
Example file output:
python3 pwforge.py --mode neural --model ../neural/master_lstm.pt --count 1000000 --min 8 --max 24 --chunk 10000 --out ../candidates.txt --no-stdout --appendBehavior:
- PWForge auto-selects CUDA if available, else CPU:
- Prints
[i] Neural mode → cudaor→ cputostderr.
- Prints
- If PyTorch is not available,
--mode neuralprints:[!] PyTorch not available – neural mode disabled- and returns no candidates.
- If
--modelis missing or invalid:- Prints
[!] --model path.pt required. Train with finetune_neural.py - Returns no candidates.
- Prints
Generation details:
- Starts each sample from a BOS token and repeatedly:
- Runs the LSTM forward,
- Softmaxes the last timestep,
- Samples characters via your RNG (
--seedaffects this).
- Stops when:
- Reaches
--max-gen-len, or - Samples EOS.
- Reaches
- If a sample is shorter than
--min, it’s padded with random printable characters, then truncated to--max. - Final candidates are filtered by length and optional entropy/dict filters, then written via the usual output flow.
Neural hyperparameters (generation-side):
--batch-size N(default: 512)--max-gen-len N(default: 32)--embed-dim N(default: 128)--hidden-dim N(default: 384)--num-layers N(default: 2)--dropout FLOAT(default: 0.3)
These must match the hyperparameters used when training the checkpoint (see Fine-Tuning a Neural Model).
Hybrid applies simple Hashcat-style rules to dictionary words and can optionally append a mask-style pattern.
python3 pwforge.py --mode hybrid --dict words.txt --rules rules.txt --mask "?l?l?d" --count 2000000 | hashcat -a 0 -m 1000 hashes.ntlmMask syntax in hybrid:
Each character in --mask is treated literally except for:
?l→ random lowercase letter?u→ random uppercase letter?d→ random digit?s→ random symbol from--symbols-file(or default set)?y→ random year from--years-file(or default range 1990-2035)?w→ random word from--dict
The generated mask part is randomly placed before or after the rule-modified word.
Weighted mixture of multiple generators in the same stream.
# 60% walk, 30% prince, 10% pcfg
python3 pwforge.py --mode combo --combo "walk:0.6,prince:0.3,pcfg:0.1" --dict words_demo.txt --pcfg-train rockyou.txt --count 3000000 | hashcat -a 0 -m 1000 hashes.ntlm--combo "mode:weight,mode:weight,..."
Weights are normalized internally.- Valid mode names here are the same as primary modes (
pw,walk,prince,markov,pcfg,mask,passphrase,numeric,syllable,mobile-walk,hybrid,neural), but:- Some modes need extra inputs (
--dict,--markov-train,--pcfg-train,--model, etc.).
- Some modes need extra inputs (
- For each candidate, PWForge:
- Randomly picks a mode according to the normalized weights,
- Generates a single candidate from that mode,
- Repeats until
--countis reached.
# Skip low-entropy strings and drop anything in denylist.txt
python3 pwforge.py --mode pw --min-entropy 2.2 --no-dict denylist.txt --count 2000000 | hashcat -a 0 -m 1000 hashes.ntlm--min-entropy <H>- Compute Shannon entropy over characters.
- Keep only candidates with entropy ≥ H.
0.0(default) = disabled.
--no-dict path.txt- Load a list of forbidden strings (case-insensitive).
- Drop any candidate whose lowercase form is present.
Filtering is applied after generation but before output and meter updates.
- If no
--out:- All candidates are printed to stdout.
- If
--out PATH:- Candidates are written to
PATH(or split variants), - And also echoed to stdout unless
--no-stdoutis set.
- Candidates are written to
--appendmakes file output use append mode if the file already exists.
# Plain file
python3 pwforge.py --mode pw --count 5000000 --out pw.txt --no-stdout
# Gzip (recommended for large runs)
python3 pwforge.py --mode walk --count 5000000 --out walks.txt.gz --gz --no-stdoutNote: gzip is auto-detected from the filename extension
.gz/.gzip.
--gzis mainly used together with--split(see below).
--split N divides each batch of generated lines into N shards and writes them to separate files.
# Split into 8 parts
python3 pwforge.py --mode pw --count 80000000 --split 8 --out pw.txt.gz --gz --no-stdout
# => pw00000000.txt.gz ... pw00000007.txt.gz- Shard file naming:
- Uses zero-padded numeric suffixes based on
N:pw00000000.txt.gz,pw00000001.txt.gz, …
- Uses zero-padded numeric suffixes based on
--appendworks with split:- Running again with
--appendwill append to the existing shard files.
- Running again with
Splitting is per call: each run of PWForge partitions its internal batch into
Nshards. If you want real parallelism, you can run PWForge multiple times in parallel (see “Parallel without GNU parallel” below).
Avoid /mnt/c/... for heavy I/O under WSL; use /home or /dev/shm:
python3 pwforge.py --mode pw --count 10000000 --out /dev/shm/pw.txt --no-stdoutIf you must access c because hashcat is installed on windows, here is a sample command line using WSL:
(base) ┌──(willard㉿REDACTED)-[/mnt/c/PenTesting/data/hashcat-6.2.6]
└─$ python3 ~/pwforge/pwforge.py --mode neural --count 10000 --min 8 --max 16 --meter 0 --model /mnt/c/PenTesting/data/
neural/master_lstm.pt 2>/dev/null | ./hashcat.exe -a 0 -m 1000 -o master.txt ../hashes/ntjtr.txt -O --self-test-disable
--bitmap-max 26 --username --session=hacktastic --keep-guessing --stdin-timeout 1These are the implemented modes in pwforge.py:
pw– Random passwords--charset,--exclude-ambiguous,--require
walk– Keyboard adjacency walks--keymap,--keymap-file,--walk-allow-shift,--starts-file,--window,--relax-backtrack,--upper,--suffix-digits
both– Combinepw+walk- The requested
--countis split:ceil(count/2)forpw,floor(count/2)forwalk.
- There is no
--both-policyflag; split behavior is fixed.
- The requested
mask– Word + year + symbol pattern--dict,--years-file,--symbols-file,--upper-first
passphrase– Multi-word passphrases--dict,--words,--sep,--upper-first
numeric– Digits onlysyllable– Pronounceable pseudo-words--template,--upper-first
prince– Word combination enumeration--dict,--prince-min/max,--sep,--bias-terms,--bias-factor,--prince-suffix-digits,--prince-symbol
markov– Character Markov model--markov-train,--markov-order
pcfg– Probabilistic Context-Free Grammar--pcfg-train, optional--pcfg-model(advanced, see below)
mobile-walk– Phone keypad walkshybrid– Rules + optional mask over dictionary--dict,--rules,--mask,--years-file,--symbols-file
combo– Weighted mixture of multiple modes--combo "mode:weight,mode:weight,..."plus relevant per-mode options
neural– Character LSTM checkpoint--model,--batch-size,--max-gen-len,--embed-dim,--hidden-dim,--num-layers,--dropout
# Pure random passwords
python3 pwforge.py --mode pw --count 1000000 | hashcat -a 0 -m 1000 hashes.ntlm
# Keyboard walks, with custom starts
python3 pwforge.py --mode walk --count 2000000 --starts-file starts.txt | hashcat -a 0 -m 1000 hashes.ntlm
# Markov over rockyou
python3 pwforge.py --mode markov --markov-train rockyou.txt --markov-order 3 --count 2000000 | hashcat -a 0 -m 0 hashes.md5
# PCFG over rockyou
python3 pwforge.py --mode pcfg --pcfg-train rockyou.txt --count 1000000 | hashcat -a 0 -m 1000 hashes.ntlm
# Neural
python3 pwforge.py --mode neural --model finetuned_model.pt --count 1000000 | hashcat -a 0 -m 1000 hashes.ntlmpython3 pwforge.py --mode prince --dict words_demo.txt --count 1000000 | john --stdin --format=nt hashes/*python3 pwforge.py --mode prince --dict words_demo.txt --count 1000000 --out prince.txt --no-stdout
hashcat -a 0 -m 1000 hashes.ntlm prince.txtUse xargs -P to run multiple processes in parallel, each with its own output file:
printf "%s
" pw walk markov pcfg | xargs -P4 -I{} sh -c '
python3 pwforge.py --mode "{}" --count 200000 --out "{}".txt --no-stdout
'--seed Ndrives the internal RNG used for:- Password lengths, character choices, walks, Markov/PCFG sampling, neural sampling (after seeding PyTorch).
- Same Python version + same seed + same options → same output sequence.
python3 pwforge.py --mode pw --seed 1337 --count 1000000 --out stable.txt --no-stdout --append- Generation happens in batches of size
--chunk(default 100k). - Larger chunks:
- Fewer I/O calls, potentially faster,
- But more memory usage.
python3 pwforge.py --mode pw --chunk 500000 --count 5000000 --out pw.txt --no-stdout --append--meter N(default100000):- Every N produced lines, PWForge prints to
stderr:[meter] 1,000,000 lines, 5,000,000 lps
- Every N produced lines, PWForge prints to
--meter 0disables the meter.
- Overrides
--countwithNwithout changing anything else. - Useful for quick tests:
# Equivalent to --count 10000
python3 pwforge.py --mode pw --count 100000000 --dry-run 10000- For
markovorpcfg, PWForge may need to train/load a model. - With
--estimate-only, it:- Prepares the models if needed,
- Prints a simple JSON object and exits, without generating candidates:
python3 pwforge.py --mode markov --markov-train rockyou.txt --min 8 --max 16 --count 1000000 --estimate-onlyOutputs something like:
{"mode": "markov", "count": 1000000, "avg_len": 12.0}This is a basic helper – it doesn’t compute a full keyspace size, just the configured avg_len and count.
pwforge.py:
- Builds an
ArgumentParserwith all options listed in Full CLI below. - Determines the effective count:
- If
--dry-run Nis set, usesN, - Else uses
--count.
- If
- Builds a target map for modes:
if mode == "both":
pw gets ceil(count/2)
walk gets floor(count/2)
else:
the selected mode gets count
Before generation, PWForge:
- Builds the charset for
pw:- From
--charset, and/or default printable classes, - Applies
--exclude-ambiguous(e.g., drop 0/O, 1/l, etc.) if enabled.
- From
- Loads:
--starts-file(forwalk),--dict(for many modes),--years-file(or defaults 1990-2035),--symbols-file(or defaults!@#$%^&*?_+-=),--bias-termsforprince,--rulesforhybrid,--no-dictinto a set for filtering.
- Builds keyboard adjacency graphs:
--keymap(qwerty,qwertz,azerty) or--keymap-fileJSON map,- Adds symbol graph if
--walk-allow-shiftis used.
- Trains or loads:
- Markov model (if
--markov-trainand mode ismarkovorestimate-only), - PCFG model:
- From
--pcfg-model(JSON) if given, else - Trains from
--pcfg-trainwhen mode ispcfgorestimate-only.
- From
- Markov model (if
For each mode with a non-zero target:
- Compute remaining
countfor that mode. - While remaining > 0:
chunk = min(CHUNK, remaining)whereCHUNK = max(1, --chunk or 100000).- Call the appropriate generator (
gen_pw,gen_walk,gen_markov,gen_neural, etc.). - Optionally apply:
--min-entropy,--no-dict.
- Call
write_output(args, lines)which:- Either writes whole
linesto one file/stdout, or - Splits into
--splitshards and writes to multiple files.
- Either writes whole
- Update counters and
meter_printer.
When --mode neural:
- Imports PyTorch safely; if not available, prints a warning and disables neural.
- Loads
CharLSTMwith your hyperparameters. - Loads
state_dictfrom--model. - Seeds PyTorch’s RNG based on PWForge’s RNG.
- Generates passwords in batches (
--batch-size) up to--max-gen-len, then clamps to--min/--max, and feeds them into the standard output/filters.
- Markov:
- Builds an n-gram character model (
--markov-order) from--markov-train. - Samples characters according to conditional probabilities of the previous
orderchars.
- Builds an n-gram character model (
- PCFG:
- Tokenizes training lines into patterns combining:
- Character classes (digits, lower, upper, symbols),
- Dictionary words & lengths.
- Learns pattern frequencies, then samples:
- A pattern (structure),
- Concrete tokens (words/digits/symbols) according to the model.
- Tokenizes training lines into patterns combining:
Both feed candidates into the same entropy/dict filters and output pipeline.
Use finetune_neural.py to create model.pt from any .txt or .gz corpus.
# Train on rockyou.txt (or any leak/corpus you are authorized to use)
python3 finetune_neural.py rockyou.txt --output finetuned_model.pt --epochs 10 --batch-size 512 --max-len 32python3 finetune_neural.py rockyou.txt --pretrained base_model.pt --output finetuned_model.pt --epochs 5 --batch-size 512 --max-len 32 --lr 0.0005python3 finetune_neural.py rockyou.txt --output finetuned_model.pt --epochs 2 --testCLI options for finetune_neural.py:
- Positional:
input– Path to corpus (.txtor.gz).
- General:
--output PATH– Output checkpoint file (default:finetuned_model.pt).--pretrained PATH– Start from an existing.ptmodel.
- Optimization:
--epochs INT(default: 10)--batch-size INT(default: 512)--lr FLOAT(default: 0.001)
- Architecture (must match generation time):
--hidden-dim INT(default: 384)--embed-dim INT(default: 128)--num-layers INT(default: 2)--dropout FLOAT(default: 0.3)--max-len INT(default: 32; training max sequence length)
- Testing:
--test– After training, sample a few passwords and print them.
Internally:
- Splits data ~95/5 into train/validation.
- Builds a character vocabulary (printable ASCII range).
- Trains a character-level LSTM with cross-entropy.
- Saves the best checkpoint by validation loss to
--output.
Below is the CLI exactly as implemented in pwforge.py.
--mode {pw,walk,both,mask,passphrase,numeric,syllable,prince,
markov,pcfg,mobile-walk,hybrid,combo,neural}
--count INT # total candidates to generate (default: 100000)
--min INT # minimum length (default: 8)
--max INT # maximum length (default: 16)
--seed INT # RNG seed for deterministic runs
--charset STR # custom charset; if omitted, uses defaults
--exclude-ambiguous # drop ambiguous chars (e.g., 0/O, 1/l, etc.)
--require "classes" # e.g. "upper,lower,digit,symbol"
# enforce required character classes
--starts-file PATH # file with starting keys (one per line)
--window INT # backtrack window (default: 2)
--relax-backtrack # allow revisiting when no non-recent neighbors
--upper # capitalize first character (walk only)
--suffix-digits INT # digits to append at end (walk only)
--keymap {qwerty,qwertz,azerty} # keyboard layout (walk only; default: qwerty)
--keymap-file PATH # JSON custom adjacency map (walk only)
--walk-allow-shift # include shifted symbol layer in graph (walk only)
mobile-walkuses a fixed phone keypad graph; it respects--windowand--relax-backtrack, but not keyboard-specific flags.
Used across mask, passphrase, prince, hybrid, some PCFG helpers:
--dict PATH # base wordlist
--years-file PATH # list of years; defaults 1990-2035 if missing
--symbols-file PATH # list of symbols; default "!@#$%^&*?_+-="
--bias-terms PATH # extra bias terms for prince
--bias-factor FLOAT # strength of bias (0..5 scaled; default: 2.0)
--words INT # passphrase word count (default: 3)
--sep STR # separator between words/tokens
--upper-first # capitalize first word/token (mask/passphrase/syllable)
--template STR # syllable template (default: "CVC")
# C = consonant, V = vowel, other chars = literal
--prince-min INT # min tokens per prince candidate (default: 2)
--prince-max INT # max tokens per prince candidate (default: 3)
--prince-suffix-digits INT# digits to append (default: 0)
--prince-symbol STR # symbol to append (default: "")
--rules PATH # Hashcat-style rules for hybrid
--mask STR # hybrid mask (uses ?l ?u ?d ?s ?y ?w tokens)
--combo "mode:weight,..." # combo mode specification
--model PATH # required checkpoint path (.pt)
--batch-size INT # batch size for sampling (default: 512)
--max-gen-len INT # max sequence length during sampling (default: 32)
--embed-dim INT # embedding size (default: 128)
--hidden-dim INT # LSTM hidden size (default: 384)
--num-layers INT # LSTM layers (default: 2)
--dropout FLOAT # dropout probability (default: 0.3)
--min-entropy FLOAT # minimum Shannon entropy (default: 0.0 = off)
--no-dict PATH # drop candidates found in this (case-insensitive) file
--out PATH # output file path
--append # append to existing output file(s)
--split INT # number of shards per batch (default: 1)
--gz # (mainly used with split; gzip auto-detected by .gz)
--no-stdout # suppress printing candidates to stdout
--dry-run INT # override count for this run only
--meter INT # progress meter interval (default: 100000; 0 = off)
--estimate-only # print JSON estimate and exit (no candidates)
--chunk INT # batch size per mode (default: 100000)
--markov-train PATH # training corpus for Markov mode
--markov-order INT # Markov order (default: 3; 1..5 recommended)
--pcfg-train PATH # training corpus for PCFG mode
--pcfg-model PATH # advanced: precomputed PCFG model JSON
# (hidden from --help via argparse.SUPPRESS)
- On WSL, use Linux paths (
/home,/dev/shm) instead of/mnt/cfor heavy output. - Use
--seedfor reproducible experiments; combine with--chunkand--splitfor predictable file layouts. - If you need multi-core performance, run multiple independent PWForge processes with different
--outor--splitsettings and usexargs -Por GNU parallel. - For John, prefer file-based workflows +
--forkfor scalability; stdin is single process.
MIT License © 2025 Adam Willard
For authorized penetration testing and research use only.