mirror of
https://github.com/trustedsec/hate_crack.git
synced 2026-07-28 14:47:22 -07:00
Merge remote-tracking branch 'origin/main'
# Conflicts: # CHANGELOG.md
This commit is contained in:
+12
-1
@@ -7,7 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
Dates are omitted for releases predating this file; see the git tags for exact timing.
|
||||
|
||||
## [2.10.11] - 2026-07-23
|
||||
## [2.11.1] - 2026-07-23
|
||||
|
||||
### Fixed
|
||||
|
||||
@@ -32,6 +32,17 @@ Dates are omitted for releases predating this file; see the git tags for exact t
|
||||
the whole upload server-side, and it raises clearly if nothing valid remains. Bundles a
|
||||
pure-Python MD4 since OpenSSL 3 dropped it from `hashlib`. Opt out with `validate=False`.
|
||||
|
||||
## [2.11.0] - 2026-07-23
|
||||
|
||||
### Changed
|
||||
|
||||
- **Shard Wordlist (Wordlist Tools option 7) now produces all shards in a single run.**
|
||||
Instead of prompting for a modulus + offset and emitting one file per invocation, it
|
||||
prompts for an output *base path* and a shard count (N), then writes all N interleaved
|
||||
parts named with zero-padded part numbers (`base.001`…`base.00N`). This matches the
|
||||
intended distributed-cracking workflow — split once, copy one part per node — without
|
||||
re-running the tool for each offset. README usage docs updated accordingly.
|
||||
|
||||
## [2.10.10] - 2026-07-21
|
||||
|
||||
### Security
|
||||
|
||||
@@ -505,7 +505,9 @@ The Wordlist Tools submenu provides 7 wordlist preprocessing utilities backed by
|
||||
|
||||
**Character class mask bits** (used by options 2 and 3): `1`=lowercase, `2`=uppercase, `4`=digit, `8`=symbol, `16`=other. Add values together: `7` = lowercase+uppercase+digit.
|
||||
|
||||
**Sharding example**: to split a wordlist across 4 nodes, run option 7 with mod=4 and offset=0 on node 1, offset=1 on node 2, etc.
|
||||
**How sharding is meant to be used**: sharding splits one wordlist into N equal, non-overlapping parts so the work can be spread across multiple machines or GPUs. Each part is *interleaved* (every N-th line), so every shard is a representative sample of the whole list rather than a contiguous front/back chunk — no single node is stuck cracking only the low-probability tail.
|
||||
|
||||
Run option 7 once, give it an input wordlist, an output base path, and a shard count (N). It writes all N parts in a single pass, named with zero-padded part numbers (`base.001`, `base.002`, … up to `base.00N`). Copy one part to each node and point that node's hashcat run at it. On a single-GPU system sharding gives no speedup, but a single part is still a fast, representative sample for a quick triage pass before committing to the full list.
|
||||
|
||||
#### Automatic Update Checks
|
||||
|
||||
@@ -896,7 +898,7 @@ A submenu of wordlist preprocessing utilities using hashcat-utils binaries. All
|
||||
| 4 | Extract Substring | Cut bytes from each word at a given offset and optional length (`cutb.bin`) |
|
||||
| 5 | Split by Length | Create per-length files in an output directory (`splitlen.bin`) |
|
||||
| 6 | Subtract Wordlist | Remove lines from a wordlist that appear in one or more remove files. Mode 1 uses `rli2.bin` (single file); mode 2 uses `rli.bin` (multiple files) |
|
||||
| 7 | Shard Wordlist | Extract every mod-th line at a given offset to create equal-sized shards (`gate.bin`) |
|
||||
| 7 | Shard Wordlist | Split a wordlist into N equal, interleaved parts in one run, written as `base.001`…`base.00N` for distributed cracking (`gate.bin`) |
|
||||
|
||||
All binaries are in `hate_crack/hashcat-utils/bin/`.
|
||||
|
||||
|
||||
+19
-13
@@ -1173,29 +1173,35 @@ def wordlist_subtract_words(ctx: Any) -> None:
|
||||
|
||||
|
||||
def wordlist_shard(ctx: Any) -> None:
|
||||
"""Prompt for input/output paths, modulus, and offset, then shard the wordlist."""
|
||||
"""Prompt for input/output base path and shard count, then write all N part files."""
|
||||
infile = ctx.select_file_with_autocomplete(
|
||||
"\n[*] Enter path to input wordlist", base_dir=ctx.hcatWordlists
|
||||
).strip()
|
||||
if not os.path.isfile(infile):
|
||||
print(f"[!] File not found: {infile}")
|
||||
return
|
||||
outfile = ctx.select_file_with_autocomplete("[*] Enter path to output wordlist").strip()
|
||||
if not outfile:
|
||||
outbase = ctx.select_file_with_autocomplete(
|
||||
"[*] Enter output base path (part numbers are appended, e.g. wl.001)"
|
||||
).strip()
|
||||
if not outbase:
|
||||
print("[!] Output path cannot be empty.")
|
||||
return
|
||||
mod = int(input("[*] Modulus (shard count, e.g. 4 for 4 shards): ").strip() or "0")
|
||||
mod = int(input("[*] Shard count (e.g. 4 to split into 4 parts): ").strip() or "0")
|
||||
if mod < 2:
|
||||
print("[!] Modulus must be at least 2.")
|
||||
print("[!] Shard count must be at least 2.")
|
||||
return
|
||||
offset = int(input("[*] Offset (shard index, 0-based, e.g. 0 for first shard): ").strip() or "0")
|
||||
if offset >= mod:
|
||||
print(f"[!] Offset must be less than modulus ({mod}).")
|
||||
return
|
||||
if ctx.wordlist_gate(infile, outfile, mod, offset):
|
||||
print(f"\n[*] Shard written to: {outfile}")
|
||||
else:
|
||||
print("[!] Shard failed.")
|
||||
width = max(3, len(str(mod)))
|
||||
written = []
|
||||
for offset in range(mod):
|
||||
outfile = f"{outbase}.{offset + 1:0{width}d}"
|
||||
if ctx.wordlist_gate(infile, outfile, mod, offset):
|
||||
written.append(outfile)
|
||||
else:
|
||||
print(f"[!] Shard failed at part {offset + 1}: {outfile}")
|
||||
return
|
||||
print(f"\n[*] Wrote {len(written)} shard(s):")
|
||||
for path in written:
|
||||
print(f" {path}")
|
||||
|
||||
|
||||
def wordlist_optimize(ctx: Any) -> None:
|
||||
|
||||
@@ -213,25 +213,43 @@ class TestWordlistSubtractWords:
|
||||
|
||||
|
||||
class TestWordlistShard:
|
||||
def test_calls_wordlist_gate_with_correct_args(self, tmp_path):
|
||||
def test_writes_all_shards_with_part_numbers(self, tmp_path):
|
||||
ctx = _make_ctx()
|
||||
infile = tmp_path / "in.txt"
|
||||
infile.write_text("word1\nword2\nword3\n")
|
||||
outfile = tmp_path / "shard.txt"
|
||||
ctx.select_file_with_autocomplete.side_effect = [str(infile), str(outfile)]
|
||||
with patch("builtins.input", side_effect=["3", "0"]):
|
||||
outbase = tmp_path / "shard.txt"
|
||||
ctx.select_file_with_autocomplete.side_effect = [str(infile), str(outbase)]
|
||||
with patch("builtins.input", side_effect=["3"]):
|
||||
wordlist_shard(ctx)
|
||||
ctx.wordlist_gate.assert_called_once_with(str(infile), str(outfile), 3, 0)
|
||||
assert ctx.wordlist_gate.call_count == 3
|
||||
calls = [c.args for c in ctx.wordlist_gate.call_args_list]
|
||||
assert calls == [
|
||||
(str(infile), f"{outbase}.001", 3, 0),
|
||||
(str(infile), f"{outbase}.002", 3, 1),
|
||||
(str(infile), f"{outbase}.003", 3, 2),
|
||||
]
|
||||
|
||||
def test_rejects_offset_gte_mod(self, tmp_path):
|
||||
def test_pad_width_grows_with_shard_count(self, tmp_path):
|
||||
ctx = _make_ctx()
|
||||
infile = tmp_path / "in.txt"
|
||||
infile.write_text("word1\n")
|
||||
outfile = tmp_path / "shard.txt"
|
||||
ctx.select_file_with_autocomplete.side_effect = [str(infile), str(outfile)]
|
||||
with patch("builtins.input", side_effect=["3", "3"]):
|
||||
outbase = tmp_path / "shard.txt"
|
||||
ctx.select_file_with_autocomplete.side_effect = [str(infile), str(outbase)]
|
||||
with patch("builtins.input", side_effect=["1000"]):
|
||||
wordlist_shard(ctx)
|
||||
ctx.wordlist_gate.assert_not_called()
|
||||
first = ctx.wordlist_gate.call_args_list[0].args[1]
|
||||
assert first == f"{outbase}.0001"
|
||||
|
||||
def test_stops_on_gate_failure(self, tmp_path):
|
||||
ctx = _make_ctx()
|
||||
ctx.wordlist_gate.side_effect = [True, False, True]
|
||||
infile = tmp_path / "in.txt"
|
||||
infile.write_text("word1\n")
|
||||
outbase = tmp_path / "shard.txt"
|
||||
ctx.select_file_with_autocomplete.side_effect = [str(infile), str(outbase)]
|
||||
with patch("builtins.input", side_effect=["3"]):
|
||||
wordlist_shard(ctx)
|
||||
assert ctx.wordlist_gate.call_count == 2
|
||||
|
||||
def test_rejects_nonexistent_infile(self, tmp_path):
|
||||
ctx = _make_ctx()
|
||||
@@ -243,9 +261,9 @@ class TestWordlistShard:
|
||||
ctx = _make_ctx()
|
||||
infile = tmp_path / "in.txt"
|
||||
infile.write_text("word1\n")
|
||||
outfile = tmp_path / "shard.txt"
|
||||
ctx.select_file_with_autocomplete.side_effect = [str(infile), str(outfile)]
|
||||
with patch("builtins.input", side_effect=["1", "0"]):
|
||||
outbase = tmp_path / "shard.txt"
|
||||
ctx.select_file_with_autocomplete.side_effect = [str(infile), str(outbase)]
|
||||
with patch("builtins.input", side_effect=["1"]):
|
||||
wordlist_shard(ctx)
|
||||
ctx.wordlist_gate.assert_not_called()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user