_version.py is gitignored but persists on disk with a stale version.
Delete it before uv sync so setuptools-scm regenerates it from the
current git tag. Also remove it in make clean for consistency.
Hashcat path is configured via config.json at runtime. The build-time
check caused false failures when running as root (sudo) or before
config.json exists, and added no value since hashcat-utils builds
independently of the hashcat installation.
The submodules-pre hashcat check now reads hcatPath and hcatBin from
config.json (mirroring main.py resolution logic) before falling back to
PATH lookup and the vendored hate_crack/hashcat/hashcat binary.
- Apply os.path.expanduser() to hcatPath so tilde (~) paths work,
consistent with every other config path (hcatWordlists, rulesDirectory,
hcatPotfilePath, hcatDebugLogPath)
- Improve error message when binary not found to show what hcatPath was
checked, making it easier to diagnose misconfiguration
- Update optimizedKernelAttacks in config.json.example to include all 21
attacks from DEFAULT_OPTIMIZED_ATTACKS (13 were added in v2.4.0 but
config.json.example was not updated, causing the runtime override to
exclude them)
- Delete block that wrongly appended found hashes back into the left
(unsolved) file - found hashes belong only in the potfile
- Fix get_hcat_potfile_path() to return "" when config key is
explicitly set to "", respecting user intent to disable potfile override
- Fix get_hcat_potfile_path() to resolve relative paths relative to the
config file directory, matching main.py's hate_path resolution
- Add potfile_path parameter to download_left_hashes() and
download_hashes_from_hashview() so CLI --potfile-path and
--no-potfile-path overrides propagate to the API merge step
- Update main.py call sites to pass hcatPotfilePath through
- Add tests covering all four bug fixes
When running as root or via sudo, /root/.local/bin may not be in PATH.
Use shutil.which with fallback to ~/.local/bin/uv, and fail clearly
if uv can't be found.
uv's PEP 517 build isolation copies source to a temp dir without .git,
so setuptools-scm can't determine the version and falls back to the
existing _version.py. Forcing --reinstall-package hate_crack makes uv
rebuild from source in the actual repo dir (editable mode) where git
is accessible, so the correct version is generated.
make clean deinits submodules and wipes the uv cache, causing
setuptools-scm to compute a wrong version during the subsequent
uv sync. The clean step is unnecessary for an upgrade - just
git pull && make install is sufficient.
Refactors the upgrade logic into _run_upgrade() so it can be called
both from the startup version check prompt and directly via --update,
bypassing the version comparison entirely.
When a newer release is found, prompt the user to upgrade in-place.
Resolves the git repo root via `git rev-parse --show-toplevel` before
running `git pull && make clean && make && make install`, so the upgrade
works correctly whether hate_crack is run from source or installed into
site-packages. Ctrl-C and EOF at the prompt continue normally.
Resolves merge conflict with origin/main (keys 19-22 from main kept,
wordlist tools submenu added at key 80).
Fixes test isolation bug in test_random_rules_attack.py where
load_cli_module() nuked hate_crack.attacks from sys.modules, breaking
__globals__ references in wordlist_tools_submenu for downstream tests.
Also corrects wrong menu key assertion (21 not 20) for generate_rules_crack.
Resolves merge conflict with origin/main (keys 19-21 used by ngram,
permute, random-rules). Combipow takes key 22.
- gzip support: decompress .gz wordlists to a temp file before passing
path to combipow.bin (which requires a filename argument)
- UI line-counting uses gzip.open for .gz files
- Update tests to reference key 22 instead of 21
Add _open_wordlist() helper that returns gzip.open() or open() based on
the .gz extension. Apply it to the three functions that open wordlist
files themselves before piping to an external binary:
- hcatPrince: prince base list fed to pp64.bin
- hcatPermute: wordlist fed to permute.bin
- hcatMarkovTrain: source file fed to hcstat2gen.bin
Attacks that pass wordlist paths directly to hashcat are unaffected -
hashcat already handles .gz natively.
Previously ngramX.bin accumulated all words from the whole file into a
flat array, producing candidates that crossed line boundaries. With a
corpus of "roses are red\nviolets are blue", it generated "red violets"
and "are red violets" - mixing lines that represent separate phrases.
Fixes ngramX.c to process each line independently so n-grams stay
within a single line, matching the expected behavior for song lyrics,
book passages, and similar per-line corpora.
- Add _is_gzipped() magic-byte detector and _wordlist_path() context
manager that transparently decompresses gzip files to a temp path
- Apply gzip handling to hcatCombinator3 and hcatCombinatorX via
contextlib.ExitStack so compressed wordlists work without manual prep
- Add hcatNgramX() wrapper using ngramX.bin <corpus> <group_size> piped
to hashcat, with gzip auto-detection on the corpus file
- Add ngram_attack() handler in attacks.py with tab-autocomplete corpus
selection and configurable group size (default 3)
- Register attack as menu option 19 in both main.py and hate_crack.py
- Fix wordlist_optimizer.py: .app extension on macOS was wrong, use .bin
- Add tests/test_ngram_gzip.py covering ngram_attack handler, _is_gzipped,
and _wordlist_path context manager (temp file cleanup, plain passthrough)
- Merge combinator, combinator3, and combinatorX into one unified
combinator_crack function that routes by wordlist count:
2 (no sep) -> hcatCombination, 3 (no sep) -> hcatCombinator3,
4+ or any separator -> hcatCombinatorX
- Replace comma-separated wordlist input with one-at-a-time
tab-autocomplete prompts (blank line to finish)
- Add _prompt_wordlist_paths helper using existing readline infrastructure
- Add hcatCombinator3Wordlist and hcatCombinatorXWordlist config vars
with rockyou.txt defaults
- Print full hashcat command to stdout in --debug mode by calling
_debug_cmd at the end of _append_potfile_arg (covers all 27 invocations)
- Collapse combinator submenu from 6 options to 4; keep combinator3_crack,
combinatorX_crack, and combinator_3plus_crack as delegation shims
- Update tests to cover unified routing and new prompt interface
Replace direct module attribute mutation with patch.object for cleaner
test isolation. Use hate_crack.main.subprocess.Popen for targeted mocking
instead of the global subprocess.Popen.
Hashcat renames ~/.hashcat/sessions/hashcat.induct after each session.
When the directory is absent the loopback test fails with "No such file
or directory". Recreate the directory in the test setup so it always
exists when the loopback command runs.
Add menu option 21 (Combipow Passphrase Attack) using combipow.bin from
hashcat-utils. Generates all unique non-empty subset combinations from a
short wordlist and pipes them into hashcat stdin.
- hcatCombipow(hash_type, hash_file, wordlist, use_space_sep) in main.py
- combipow_crack handler in attacks.py validates file, enforces 63-line
limit, prompts for space separator, then delegates to hcatCombipow
- Wired into get_main_menu_items() and get_main_menu_options() in both
main.py and hate_crack.py
- 12 tests covering menu presence, handler delegation, line-count
enforcement, file validation, and subprocess flag construction
Extends the combinator submenu (option 6) with two new attacks using
hashcat-utils binaries that were already compiled but unused.
- hcatCombinator3: 3-way wordlist combination via combinator3.bin piped
to hashcat stdin
- hcatCombinatorX: 2-8 wordlist combination via combinatorX.bin with
optional --sepFill separator, piped to hashcat stdin
- combinator3_crack handler: prompts for 3 comma-separated wordlist paths
- combinatorX_crack handler: prompts for 2-8 paths plus optional separator
- combinator_submenu updated with options 5 and 6
Closes#84, closes#85
Adds Permutation Attack (menu option 19) that generates all character
permutations of each word in a targeted wordlist and pipes them to
hashcat via permute.bin from hashcat-utils.
- hcatPermute() in main.py: pipes permute.bin < wordlist | hashcat
- permute_crack() in attacks.py: prompts for single wordlist file with
factorial-growth warning, tab-autocomplete support
- Menu option 19 wired in both main.py and hate_crack.py
- hcatPermuteCount tracking alongside other count globals
- Tests: test_permute_attack.py (handler behavior) and
test_permute_wrapper.py (subprocess wiring)
- README: added entry in menu listing and attack descriptions
- Add hcatGenerateRules() in main.py: runs generate-rules.bin to
produce N random rules, writes them to a temp file, runs hashcat
with -r against a chosen wordlist, cleans up on exit
- Add generate_rules_crack() handler in attacks.py with count
prompt (default 65536), wordlist picker with tab-completion,
input validation, and abort on invalid input
- Add dispatcher generate_rules_crack() in main.py and key "20"
in both main.py and hate_crack.py get_main_menu_options()
- Add ("20", "Random Rules Attack") to get_main_menu_items()
- Add tests: test_random_rules_attack.py (menu presence, handler
wiring), test_random_rules_wrapper.py (subprocess behavior,
cleanup, count passing, count tracking)
- Add key "20" to MENU_OPTION_TEST_CASES in test_ui_menu_options.py
- Update README: add option 20 to menu listing, add attack
description, add version history entry
Adds Rule File Tools submenu (menu option 81) with three operations:
- Clean: removes invalid/duplicate rules via cleanup-rules.bin
- Optimize: consolidates redundant operations via rules_optimize.bin
- Clean and optimize: both in sequence with temp file handling
Wired through the standard three-layer pattern: main.py utility
functions + dispatcher, attacks.py handlers + submenu, root
hate_crack.py menu registration.
hashcat's Lzma2Decode() expects a raw LZMA2 stream starting with 0xff chunk
headers, not an XZ container format (0xfd 37 7a...). FORMAT_RAW with
FILTER_LZMA2 produces the correct format.