hashcrack-cli 0.1.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
hashcrack/__init__.py ADDED
@@ -0,0 +1,2 @@
1
+ __version__ = "0.1.1"
2
+ __author__ = "shazeus"
hashcrack/__main__.py ADDED
@@ -0,0 +1,4 @@
1
+ from hashcrack.cli import cli
2
+
3
+ if __name__ == "__main__":
4
+ cli()
hashcrack/cli.py ADDED
@@ -0,0 +1,700 @@
1
+ import sys
2
+ import hashlib
3
+ import json
4
+ import time
5
+ from pathlib import Path
6
+ from typing import Optional
7
+
8
+ import click
9
+ from rich.console import Console
10
+ from rich.table import Table
11
+ from rich.panel import Panel
12
+ from rich.progress import Progress, SpinnerColumn, TextColumn
13
+ from rich.text import Text
14
+ from rich import box
15
+ from rich.columns import Columns
16
+
17
+ from hashcrack import __version__
18
+ from hashcrack.detector import detect_hash, classify_by_length, HASH_PATTERNS
19
+ from hashcrack.lookup import lookup_hash, generate_hash, verify_hash
20
+
21
+ console = Console()
22
+ err_console = Console(stderr=True)
23
+
24
+ CONTEXT_SETTINGS = {"help_option_names": ["-h", "--help"]}
25
+
26
+ ALGO_CHOICES = [
27
+ "md5", "sha1", "sha224", "sha256", "sha384", "sha512",
28
+ "sha3-256", "sha3-512", "blake2b", "blake2s",
29
+ ]
30
+
31
+ TYPE_COLORS = {
32
+ "MD5": "cyan",
33
+ "SHA-1": "green",
34
+ "SHA-256": "bright_green",
35
+ "SHA-512": "bright_blue",
36
+ "bcrypt": "yellow",
37
+ "Argon2": "magenta",
38
+ "NTLM": "red",
39
+ "LM Hash": "bright_red",
40
+ "Base64": "white",
41
+ }
42
+
43
+
44
+ def _color_for(name: str) -> str:
45
+ return TYPE_COLORS.get(name, "bright_white")
46
+
47
+
48
+ @click.group(context_settings=CONTEXT_SETTINGS)
49
+ @click.version_option(__version__, "-V", "--version", prog_name="hashcrack")
50
+ def cli():
51
+ """hashcrack — Hash identification and lookup tool for CTF and security research.
52
+
53
+ \b
54
+ Identify hash types, look up plaintexts, generate hashes, and more.
55
+ """
56
+
57
+
58
+ # ─────────────────────────────────────────────
59
+ # identify
60
+ # ─────────────────────────────────────────────
61
+ @cli.command("identify")
62
+ @click.argument("hash_string")
63
+ @click.option("--json", "output_json", is_flag=True, help="Output as JSON.")
64
+ def identify(hash_string: str, output_json: bool):
65
+ """Identify the type(s) of a given hash.
66
+
67
+ \b
68
+ Examples:
69
+ hashcrack identify 5d41402abc4b2a76b9719d911017c592
70
+ hashcrack identify '$2a$12$R9h/cIPz0gi.URNNX3kh2OPST9/PgBkqquzi.Ss7KIUgO2t0jWMUW'
71
+ """
72
+ hash_string = hash_string.strip()
73
+ matches = detect_hash(hash_string)
74
+
75
+ if output_json:
76
+ click.echo(json.dumps({"hash": hash_string, "matches": matches}, indent=2))
77
+ return
78
+
79
+ length = len(hash_string)
80
+ console.print(Panel(
81
+ f"[bold yellow]{hash_string}[/bold yellow]",
82
+ title="[bold]Hash Input[/bold]",
83
+ subtitle=f"Length: {length} chars",
84
+ border_style="blue",
85
+ ))
86
+
87
+ if not matches:
88
+ console.print("[red]No known hash type matched.[/red]")
89
+ console.print(f"[dim]Hash length {length} — try checking for encoding issues.[/dim]")
90
+ sys.exit(1)
91
+
92
+ table = Table(
93
+ title=f"[bold green]Possible Hash Types ({len(matches)} match{'es' if len(matches) != 1 else ''})[/bold green]",
94
+ box=box.ROUNDED,
95
+ border_style="green",
96
+ header_style="bold magenta",
97
+ )
98
+ table.add_column("Type", style="bold", min_width=14)
99
+ table.add_column("Description", style="dim")
100
+ table.add_column("Bits", justify="right", style="cyan")
101
+
102
+ bits_map = {32: 128, 40: 160, 48: 192, 56: 224, 64: 256, 96: 384, 128: 512}
103
+
104
+ for m in matches:
105
+ bits = bits_map.get(m.get("length", 0), "—") if m.get("length") else "variable"
106
+ color = _color_for(m["name"])
107
+ table.add_row(
108
+ f"[{color}]{m['name']}[/{color}]",
109
+ m["description"],
110
+ str(bits),
111
+ )
112
+
113
+ console.print(table)
114
+
115
+ if len(matches) > 1:
116
+ console.print(
117
+ "[dim]Tip: Multiple types share the same length. Use [bold]hashcrack lookup[/bold] "
118
+ "or context clues to narrow down.[/dim]"
119
+ )
120
+
121
+
122
+ # ─────────────────────────────────────────────
123
+ # lookup
124
+ # ─────────────────────────────────────────────
125
+ @cli.command("lookup")
126
+ @click.argument("hash_string")
127
+ @click.option("--json", "output_json", is_flag=True, help="Output as JSON.")
128
+ @click.option("--quiet", "-q", is_flag=True, help="Only print plaintext if found.")
129
+ def lookup(hash_string: str, output_json: bool, quiet: bool):
130
+ """Look up plaintext for a hash via public databases.
131
+
132
+ \b
133
+ Searches multiple hash cracking APIs automatically.
134
+
135
+ \b
136
+ Examples:
137
+ hashcrack lookup 5d41402abc4b2a76b9719d911017c592
138
+ hashcrack lookup aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d
139
+ """
140
+ hash_string = hash_string.strip()
141
+
142
+ if not quiet:
143
+ matches = detect_hash(hash_string)
144
+ types = ", ".join(m["name"] for m in matches) if matches else "Unknown"
145
+ console.print(Panel(
146
+ f"[bold yellow]{hash_string}[/bold yellow]\n[dim]Detected types: {types}[/dim]",
147
+ title="[bold]Hash Lookup[/bold]",
148
+ border_style="blue",
149
+ ))
150
+
151
+ with Progress(
152
+ SpinnerColumn(),
153
+ TextColumn("[progress.description]{task.description}"),
154
+ transient=True,
155
+ console=console,
156
+ ) as progress:
157
+ if not quiet:
158
+ task = progress.add_task("Querying hash databases...", total=None)
159
+ result = lookup_hash(hash_string)
160
+
161
+ if output_json:
162
+ click.echo(json.dumps({"hash": hash_string, **result}, indent=2))
163
+ return
164
+
165
+ if result["found"]:
166
+ if quiet:
167
+ click.echo(result["plaintext"])
168
+ else:
169
+ console.print(Panel(
170
+ f"[bold green]CRACKED![/bold green]\n\n"
171
+ f"Plaintext: [bold bright_green]{result['plaintext']}[/bold bright_green]\n"
172
+ f"Source: [dim]{result['source']}[/dim]",
173
+ title="[bold green]Result[/bold green]",
174
+ border_style="green",
175
+ ))
176
+ else:
177
+ if quiet:
178
+ sys.exit(1)
179
+ else:
180
+ console.print(Panel(
181
+ "[red]Not found in public databases.[/red]\n\n"
182
+ "[dim]The hash may be:\n"
183
+ " • Using a strong algorithm (bcrypt, Argon2, scrypt)\n"
184
+ " • A custom or salted hash\n"
185
+ " • Not in any public rainbow table[/dim]",
186
+ title="[bold red]Not Found[/bold red]",
187
+ border_style="red",
188
+ ))
189
+ sys.exit(1)
190
+
191
+
192
+ # ─────────────────────────────────────────────
193
+ # generate
194
+ # ─────────────────────────────────────────────
195
+ @cli.command("generate")
196
+ @click.argument("text")
197
+ @click.option(
198
+ "--algo", "-a",
199
+ multiple=True,
200
+ type=click.Choice(ALGO_CHOICES, case_sensitive=False),
201
+ help="Algorithm(s) to use. Repeat for multiple. Default: all common.",
202
+ )
203
+ @click.option("--all", "all_algos", is_flag=True, help="Generate all supported hashes.")
204
+ @click.option("--json", "output_json", is_flag=True, help="Output as JSON.")
205
+ def generate(text: str, algo: tuple, all_algos: bool, output_json: bool):
206
+ """Generate hash(es) for a given string.
207
+
208
+ \b
209
+ Examples:
210
+ hashcrack generate "hello world"
211
+ hashcrack generate "password" --algo md5 --algo sha256
212
+ hashcrack generate "secret" --all
213
+ """
214
+ if all_algos or not algo:
215
+ algorithms = ALGO_CHOICES
216
+ else:
217
+ algorithms = list(algo)
218
+
219
+ results = {}
220
+ for a in algorithms:
221
+ h = generate_hash(text, a)
222
+ if h:
223
+ results[a] = h
224
+
225
+ if output_json:
226
+ click.echo(json.dumps({"input": text, "hashes": results}, indent=2))
227
+ return
228
+
229
+ table = Table(
230
+ title=f"[bold]Hashes for:[/bold] [yellow]{text}[/yellow]",
231
+ box=box.ROUNDED,
232
+ border_style="cyan",
233
+ header_style="bold magenta",
234
+ )
235
+ table.add_column("Algorithm", style="bold cyan", min_width=12)
236
+ table.add_column("Hash", style="white")
237
+
238
+ for algo_name, hash_val in results.items():
239
+ table.add_row(algo_name.upper(), hash_val)
240
+
241
+ console.print(table)
242
+
243
+
244
+ # ─────────────────────────────────────────────
245
+ # batch
246
+ # ─────────────────────────────────────────────
247
+ @cli.command("batch")
248
+ @click.argument("input_file", type=click.Path(exists=True))
249
+ @click.option("--lookup", "do_lookup", is_flag=True, help="Also attempt to look up plaintexts.")
250
+ @click.option("--output", "-o", type=click.Path(), help="Save results to a file (JSON).")
251
+ @click.option("--json", "output_json", is_flag=True, help="Output as JSON.")
252
+ def batch(input_file: str, do_lookup: bool, output: Optional[str], output_json: bool):
253
+ """Process multiple hashes from a file (one per line).
254
+
255
+ \b
256
+ Examples:
257
+ hashcrack batch hashes.txt
258
+ hashcrack batch hashes.txt --lookup
259
+ hashcrack batch hashes.txt --lookup --output results.json
260
+ """
261
+ path = Path(input_file)
262
+ lines = [l.strip() for l in path.read_text().splitlines() if l.strip() and not l.startswith("#")]
263
+
264
+ if not lines:
265
+ if output_json:
266
+ click.echo("[]")
267
+ return
268
+ console.print("[red]No hashes found in file.[/red]")
269
+ sys.exit(1)
270
+
271
+ if not output_json:
272
+ console.print(Panel(
273
+ f"Processing [bold]{len(lines)}[/bold] hash(es) from [cyan]{path.name}[/cyan]",
274
+ title="[bold]Batch Mode[/bold]",
275
+ border_style="blue",
276
+ ))
277
+
278
+ all_results = []
279
+
280
+ if output_json:
281
+ for line in lines:
282
+ matches = detect_hash(line)
283
+ entry = {
284
+ "hash": line,
285
+ "types": [m["name"] for m in matches],
286
+ "found": False,
287
+ "plaintext": None,
288
+ "source": None,
289
+ }
290
+ if do_lookup and matches:
291
+ result = lookup_hash(line)
292
+ entry.update(result)
293
+ all_results.append(entry)
294
+ else:
295
+ with Progress(
296
+ SpinnerColumn(),
297
+ TextColumn("[progress.description]{task.description}"),
298
+ console=console,
299
+ ) as progress:
300
+ task = progress.add_task("Processing...", total=len(lines))
301
+
302
+ for i, line in enumerate(lines, 1):
303
+ progress.update(task, description=f"[{i}/{len(lines)}] {line[:32]}...")
304
+ matches = detect_hash(line)
305
+ entry = {
306
+ "hash": line,
307
+ "types": [m["name"] for m in matches],
308
+ "found": False,
309
+ "plaintext": None,
310
+ "source": None,
311
+ }
312
+ if do_lookup and matches:
313
+ result = lookup_hash(line)
314
+ entry.update(result)
315
+ all_results.append(entry)
316
+ progress.advance(task)
317
+
318
+ if output_json:
319
+ click.echo(json.dumps(all_results, indent=2))
320
+ else:
321
+ table = Table(
322
+ title=f"[bold]Batch Results — {len(all_results)} hashes[/bold]",
323
+ box=box.ROUNDED,
324
+ border_style="cyan",
325
+ header_style="bold magenta",
326
+ )
327
+ table.add_column("Hash", style="dim", max_width=40)
328
+ table.add_column("Detected Types", style="cyan")
329
+ if do_lookup:
330
+ table.add_column("Plaintext", style="bold green")
331
+ table.add_column("Source", style="dim")
332
+
333
+ for entry in all_results:
334
+ short_hash = entry["hash"][:36] + "..." if len(entry["hash"]) > 36 else entry["hash"]
335
+ types_str = ", ".join(entry["types"]) if entry["types"] else "[red]Unknown[/red]"
336
+ if do_lookup:
337
+ plaintext = f"[green]{entry['plaintext']}[/green]" if entry["found"] else "[red]Not found[/red]"
338
+ source = entry["source"] or ""
339
+ table.add_row(short_hash, types_str, plaintext, source)
340
+ else:
341
+ table.add_row(short_hash, types_str)
342
+
343
+ console.print(table)
344
+
345
+ if output:
346
+ Path(output).write_text(json.dumps(all_results, indent=2))
347
+ if not output_json:
348
+ console.print(f"\n[dim]Results saved to [cyan]{output}[/cyan][/dim]")
349
+
350
+ if do_lookup and not output_json:
351
+ found_count = sum(1 for r in all_results if r["found"])
352
+ console.print(
353
+ f"\n[bold]Summary:[/bold] {found_count}/{len(all_results)} hashes cracked "
354
+ f"[dim]({100*found_count//len(all_results) if all_results else 0}%)[/dim]"
355
+ )
356
+
357
+
358
+ # ─────────────────────────────────────────────
359
+ # verify
360
+ # ─────────────────────────────────────────────
361
+ @cli.command("verify")
362
+ @click.argument("hash_string")
363
+ @click.argument("plaintext")
364
+ @click.option(
365
+ "--algo", "-a",
366
+ type=click.Choice(ALGO_CHOICES, case_sensitive=False),
367
+ help="Force a specific algorithm.",
368
+ )
369
+ @click.option("--json", "output_json", is_flag=True, help="Output as JSON.")
370
+ def verify(hash_string: str, plaintext: str, algo: Optional[str], output_json: bool):
371
+ """Verify that a plaintext matches a hash.
372
+
373
+ \b
374
+ Examples:
375
+ hashcrack verify 5d41402abc4b2a76b9719d911017c592 "hello"
376
+ hashcrack verify <sha256_hash> "password" --algo sha256
377
+ """
378
+ hash_string = hash_string.strip()
379
+
380
+ if algo:
381
+ algorithms_to_try = [algo]
382
+ else:
383
+ matches = detect_hash(hash_string)
384
+ if not matches:
385
+ err_console.print("[red]Cannot detect hash type. Use --algo to specify.[/red]")
386
+ sys.exit(1)
387
+ algorithms_to_try = [m["name"].lower().replace("-", "").replace(" ", "")
388
+ for m in matches if m.get("length")]
389
+
390
+ verified = False
391
+ matched_algo = None
392
+
393
+ for a in algorithms_to_try:
394
+ clean_algo = a.lower().replace("-", "").replace("_", "").replace(" ", "")
395
+ if verify_hash(hash_string, plaintext, clean_algo):
396
+ verified = True
397
+ matched_algo = a
398
+ break
399
+
400
+ if output_json:
401
+ click.echo(json.dumps({
402
+ "hash": hash_string,
403
+ "plaintext": plaintext,
404
+ "verified": verified,
405
+ "algorithm": matched_algo,
406
+ }, indent=2))
407
+ return
408
+
409
+ if verified:
410
+ console.print(Panel(
411
+ f"[bold green]MATCH![/bold green]\n\n"
412
+ f"Hash: [dim]{hash_string}[/dim]\n"
413
+ f"Plaintext: [bold green]{plaintext}[/bold green]\n"
414
+ f"Algorithm: [cyan]{matched_algo}[/cyan]",
415
+ title="[bold green]Verification[/bold green]",
416
+ border_style="green",
417
+ ))
418
+ else:
419
+ console.print(Panel(
420
+ f"[bold red]NO MATCH[/bold red]\n\n"
421
+ f"Hash: [dim]{hash_string}[/dim]\n"
422
+ f"Plaintext: [yellow]{plaintext}[/yellow]\n"
423
+ f"Tried: [dim]{', '.join(algorithms_to_try)}[/dim]",
424
+ title="[bold red]Verification Failed[/bold red]",
425
+ border_style="red",
426
+ ))
427
+ sys.exit(1)
428
+
429
+
430
+ # ─────────────────────────────────────────────
431
+ # info
432
+ # ─────────────────────────────────────────────
433
+ @cli.command("info")
434
+ @click.argument("hash_type", required=False)
435
+ @click.option("--json", "output_json", is_flag=True, help="Output as JSON.")
436
+ def info(hash_type: Optional[str], output_json: bool):
437
+ """Show information about a hash algorithm, or list all supported types.
438
+
439
+ \b
440
+ Examples:
441
+ hashcrack info # list all supported hash types
442
+ hashcrack info md5 # details on MD5
443
+ hashcrack info sha256
444
+ """
445
+ if hash_type:
446
+ target = hash_type.upper().replace("_", "-").replace(" ", "-")
447
+ matches = [p for p in HASH_PATTERNS if p["name"].upper() == target]
448
+
449
+ if not matches:
450
+ # Try fuzzy
451
+ matches = [p for p in HASH_PATTERNS if target in p["name"].upper()]
452
+
453
+ if not matches:
454
+ err_console.print(f"[red]Unknown hash type: {hash_type}[/red]")
455
+ err_console.print("[dim]Run [bold]hashcrack info[/bold] to list all types.[/dim]")
456
+ sys.exit(1)
457
+
458
+ m = matches[0]
459
+ if output_json:
460
+ click.echo(json.dumps(m, indent=2))
461
+ return
462
+
463
+ bits_map = {32: 128, 40: 160, 48: 192, 56: 224, 64: 256, 96: 384, 128: 512}
464
+ bits = bits_map.get(m.get("length", 0), "variable")
465
+ color = _color_for(m["name"])
466
+
467
+ console.print(Panel(
468
+ f"[bold {color}]{m['name']}[/bold {color}]\n\n"
469
+ f"[dim]Description:[/dim] {m['description']}\n"
470
+ f"[dim]Hex length:[/dim] {m.get('length', 'variable')}\n"
471
+ f"[dim]Bits:[/dim] {bits}\n"
472
+ f"[dim]Pattern:[/dim] [dim]{m['regex']}[/dim]\n\n"
473
+ f"[dim]Example:[/dim]\n[yellow]{m['example']}[/yellow]",
474
+ title=f"[bold]Hash Info — {m['name']}[/bold]",
475
+ border_style=color,
476
+ ))
477
+ else:
478
+ if output_json:
479
+ click.echo(json.dumps(HASH_PATTERNS, indent=2))
480
+ return
481
+
482
+ table = Table(
483
+ title="[bold]Supported Hash Types[/bold]",
484
+ box=box.ROUNDED,
485
+ border_style="blue",
486
+ header_style="bold magenta",
487
+ )
488
+ table.add_column("Type", style="bold", min_width=14)
489
+ table.add_column("Bits", justify="right", style="cyan")
490
+ table.add_column("Hex Length", justify="right", style="dim")
491
+ table.add_column("Description", style="white")
492
+
493
+ bits_map = {32: 128, 40: 160, 48: 192, 56: 224, 64: 256, 96: 384, 128: 512}
494
+
495
+ for p in HASH_PATTERNS:
496
+ bits = bits_map.get(p.get("length", 0), "var")
497
+ color = _color_for(p["name"])
498
+ table.add_row(
499
+ f"[{color}]{p['name']}[/{color}]",
500
+ str(bits),
501
+ str(p.get("length", "—")),
502
+ p["description"],
503
+ )
504
+
505
+ console.print(table)
506
+ console.print(f"\n[dim]Total: {len(HASH_PATTERNS)} hash types. Run [bold]hashcrack info <type>[/bold] for details.[/dim]")
507
+
508
+
509
+ # ─────────────────────────────────────────────
510
+ # analyze
511
+ # ─────────────────────────────────────────────
512
+ @cli.command("analyze")
513
+ @click.argument("hash_string")
514
+ @click.option("--json", "output_json", is_flag=True, help="Output as JSON.")
515
+ def analyze(hash_string: str, output_json: bool):
516
+ """Deep analysis of a hash: type detection + lookup + entropy.
517
+
518
+ \b
519
+ Combines identify, lookup, and statistical analysis in one command.
520
+
521
+ \b
522
+ Examples:
523
+ hashcrack analyze 5d41402abc4b2a76b9719d911017c592
524
+ """
525
+ import math
526
+
527
+ hash_string = hash_string.strip()
528
+ matches = detect_hash(hash_string)
529
+
530
+ # Entropy calculation
531
+ freq = {}
532
+ for ch in hash_string.lower():
533
+ freq[ch] = freq.get(ch, 0) + 1
534
+ length = len(hash_string)
535
+ entropy = 0.0
536
+ for count in freq.values():
537
+ p = count / length
538
+ if p > 0:
539
+ entropy -= p * math.log2(p)
540
+
541
+ # Character distribution
542
+ hex_chars = sum(1 for c in hash_string if c in "0123456789abcdefABCDEF")
543
+ is_pure_hex = hex_chars == length
544
+
545
+ result = {
546
+ "hash": hash_string,
547
+ "length": length,
548
+ "entropy": round(entropy, 4),
549
+ "is_pure_hex": is_pure_hex,
550
+ "detected_types": [m["name"] for m in matches],
551
+ "lookup": None,
552
+ }
553
+
554
+ console.print(Panel(
555
+ f"[bold yellow]{hash_string}[/bold yellow]",
556
+ title="[bold]Deep Hash Analysis[/bold]",
557
+ border_style="magenta",
558
+ ))
559
+
560
+ # Stats table
561
+ stats = Table(box=box.SIMPLE, show_header=False)
562
+ stats.add_column("Property", style="dim")
563
+ stats.add_column("Value", style="bold")
564
+
565
+ stats.add_row("Length", f"{length} characters")
566
+ stats.add_row("Entropy", f"{entropy:.2f} bits/char")
567
+ stats.add_row("Pure hex", "Yes" if is_pure_hex else "No")
568
+ stats.add_row("Charset", _describe_charset(hash_string))
569
+
570
+ console.print(stats)
571
+
572
+ if matches:
573
+ console.print(f"\n[bold green]Detected Types:[/bold green]")
574
+ for m in matches:
575
+ color = _color_for(m["name"])
576
+ console.print(f" [{color}]●[/{color}] [bold]{m['name']}[/bold] — {m['description']}")
577
+ else:
578
+ console.print("[yellow]No known hash type matched.[/yellow]")
579
+
580
+ # Attempt lookup for hex hashes
581
+ if is_pure_hex and length in (32, 40, 64, 128):
582
+ console.print("\n[dim]Attempting lookup in public databases...[/dim]")
583
+ lookup_result = lookup_hash(hash_string)
584
+ result["lookup"] = lookup_result
585
+ if lookup_result["found"]:
586
+ console.print(Panel(
587
+ f"[bold green]CRACKED![/bold green] Plaintext: [bright_green]{lookup_result['plaintext']}[/bright_green]\n"
588
+ f"[dim]Source: {lookup_result['source']}[/dim]",
589
+ border_style="green",
590
+ ))
591
+ else:
592
+ console.print("[dim]Not found in public databases.[/dim]")
593
+
594
+ if output_json:
595
+ click.echo(json.dumps(result, indent=2))
596
+
597
+
598
+ def _describe_charset(s: str) -> str:
599
+ has_lower = any(c.islower() for c in s)
600
+ has_upper = any(c.isupper() for c in s)
601
+ has_digit = any(c.isdigit() for c in s)
602
+ has_special = any(not c.isalnum() for c in s)
603
+ parts = []
604
+ if has_lower:
605
+ parts.append("lowercase")
606
+ if has_upper:
607
+ parts.append("uppercase")
608
+ if has_digit:
609
+ parts.append("digits")
610
+ if has_special:
611
+ parts.append("special")
612
+ return ", ".join(parts) if parts else "unknown"
613
+
614
+
615
+ # ─────────────────────────────────────────────
616
+ # wordlist
617
+ # ─────────────────────────────────────────────
618
+ @cli.command("wordlist")
619
+ @click.argument("wordlist_file", type=click.Path(exists=True))
620
+ @click.argument("hash_string")
621
+ @click.option(
622
+ "--algo", "-a",
623
+ type=click.Choice(ALGO_CHOICES, case_sensitive=False),
624
+ default="md5",
625
+ show_default=True,
626
+ help="Hash algorithm to use.",
627
+ )
628
+ @click.option("--json", "output_json", is_flag=True, help="Output as JSON.")
629
+ def wordlist(wordlist_file: str, hash_string: str, algo: str, output_json: bool):
630
+ """Crack a hash using a local wordlist file.
631
+
632
+ \b
633
+ Hashes each line of the wordlist and compares against the target.
634
+
635
+ \b
636
+ Examples:
637
+ hashcrack wordlist rockyou.txt 5d41402abc4b2a76b9719d911017c592
638
+ hashcrack wordlist passwords.txt <sha256_hash> --algo sha256
639
+ """
640
+ hash_string = hash_string.strip().lower()
641
+ path = Path(wordlist_file)
642
+
643
+ total_lines = sum(1 for _ in path.open("r", errors="ignore"))
644
+
645
+ console.print(Panel(
646
+ f"Target: [bold yellow]{hash_string}[/bold yellow]\n"
647
+ f"Algorithm: [cyan]{algo.upper()}[/cyan]\n"
648
+ f"Wordlist: [dim]{path.name}[/dim] ([bold]{total_lines:,}[/bold] words)",
649
+ title="[bold]Wordlist Attack[/bold]",
650
+ border_style="blue",
651
+ ))
652
+
653
+ found = False
654
+ plaintext = None
655
+ tried = 0
656
+
657
+ with Progress(
658
+ SpinnerColumn(),
659
+ TextColumn("[progress.description]{task.description}"),
660
+ console=console,
661
+ ) as progress:
662
+ task = progress.add_task("Cracking...", total=total_lines)
663
+
664
+ with path.open("r", errors="ignore") as f:
665
+ for line in f:
666
+ word = line.rstrip("\n\r")
667
+ computed = generate_hash(word, algo)
668
+ tried += 1
669
+ progress.advance(task)
670
+
671
+ if computed and computed.lower() == hash_string:
672
+ found = True
673
+ plaintext = word
674
+ break
675
+
676
+ if output_json:
677
+ click.echo(json.dumps({
678
+ "hash": hash_string,
679
+ "algorithm": algo,
680
+ "found": found,
681
+ "plaintext": plaintext,
682
+ "tried": tried,
683
+ }, indent=2))
684
+ return
685
+
686
+ if found:
687
+ console.print(Panel(
688
+ f"[bold green]CRACKED![/bold green]\n\n"
689
+ f"Plaintext: [bold bright_green]{plaintext}[/bold bright_green]\n"
690
+ f"Tried: [dim]{tried:,} words[/dim]",
691
+ title="[bold green]Success[/bold green]",
692
+ border_style="green",
693
+ ))
694
+ else:
695
+ console.print(Panel(
696
+ f"[red]Not found in wordlist.[/red]\n[dim]Tried {tried:,} words.[/dim]",
697
+ title="[bold red]Not Found[/bold red]",
698
+ border_style="red",
699
+ ))
700
+ sys.exit(1)
hashcrack/detector.py ADDED
@@ -0,0 +1,223 @@
1
+ import re
2
+ from typing import Optional
3
+
4
+ HASH_PATTERNS = [
5
+ {
6
+ "name": "MD5",
7
+ "regex": r"^[a-f0-9]{32}$",
8
+ "length": 32,
9
+ "description": "Message Digest 5",
10
+ "example": "5d41402abc4b2a76b9719d911017c592",
11
+ },
12
+ {
13
+ "name": "SHA-1",
14
+ "regex": r"^[a-f0-9]{40}$",
15
+ "length": 40,
16
+ "description": "Secure Hash Algorithm 1",
17
+ "example": "aaf4c61ddcc5e8a2dabede0f3b482cd9aea9434d",
18
+ },
19
+ {
20
+ "name": "SHA-224",
21
+ "regex": r"^[a-f0-9]{56}$",
22
+ "length": 56,
23
+ "description": "Secure Hash Algorithm 224",
24
+ "example": "abd37534c7d9a2efb9465de931cd7055ffdb8879563ae98078d6d6d5",
25
+ },
26
+ {
27
+ "name": "SHA-256",
28
+ "regex": r"^[a-f0-9]{64}$",
29
+ "length": 64,
30
+ "description": "Secure Hash Algorithm 256",
31
+ "example": "2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824",
32
+ },
33
+ {
34
+ "name": "SHA-384",
35
+ "regex": r"^[a-f0-9]{96}$",
36
+ "length": 96,
37
+ "description": "Secure Hash Algorithm 384",
38
+ "example": "59e1748777448c69de6b800d7a33bbfb9ff1b463e44354c3553bcdb9c666fa90125a3c79f90397bdf5f6a13de828684f",
39
+ },
40
+ {
41
+ "name": "SHA-512",
42
+ "regex": r"^[a-f0-9]{128}$",
43
+ "length": 128,
44
+ "description": "Secure Hash Algorithm 512",
45
+ "example": "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e",
46
+ },
47
+ {
48
+ "name": "SHA3-256",
49
+ "regex": r"^[a-f0-9]{64}$",
50
+ "length": 64,
51
+ "description": "SHA3 256-bit (Keccak)",
52
+ "example": "a7ffc6f8bf1ed76651c14756a061d662f580ff4de43b49fa82d80a4b80f8434a",
53
+ },
54
+ {
55
+ "name": "SHA3-512",
56
+ "regex": r"^[a-f0-9]{128}$",
57
+ "length": 128,
58
+ "description": "SHA3 512-bit (Keccak)",
59
+ "example": "a69f73cca23a9ac5c8b567dc185a756e97c982164fe25859e0d1dcc1475c80a615b2123af1f5f94c11e3e9402c3ac558f500199d95b6d3e301758586281dcd26",
60
+ },
61
+ {
62
+ "name": "NTLM",
63
+ "regex": r"^[a-f0-9]{32}$",
64
+ "length": 32,
65
+ "description": "NT LAN Manager hash (Windows auth)",
66
+ "example": "b4b9b02e6f09a9bd760f388b67351e2b",
67
+ },
68
+ {
69
+ "name": "MySQL4/5",
70
+ "regex": r"^\*[a-f0-9]{40}$",
71
+ "length": 41,
72
+ "description": "MySQL 4.1+ password hash",
73
+ "example": "*900ADE9CA5AE4B40B0E6A8D4E1FEC32DFEA38E4E",
74
+ },
75
+ {
76
+ "name": "bcrypt",
77
+ "regex": r"^\$2[ayb]\$.{56}$",
78
+ "length": None,
79
+ "description": "bcrypt password hash",
80
+ "example": "$2a$12$R9h/cIPz0gi.URNNX3kh2OPST9/PgBkqquzi.Ss7KIUgO2t0jWMUW",
81
+ },
82
+ {
83
+ "name": "Argon2",
84
+ "regex": r"^\$argon2(i|d|id)\$",
85
+ "length": None,
86
+ "description": "Argon2 password hash",
87
+ "example": "$argon2id$v=19$m=65536,t=2,p=1$c29tZXNhbHQ$RdescudvJCsgt3ub+b+dWRWJTmaaJObG",
88
+ },
89
+ {
90
+ "name": "scrypt",
91
+ "regex": r"^\$scrypt\$",
92
+ "length": None,
93
+ "description": "scrypt password hash",
94
+ "example": "$scrypt$ln=16,r=8,p=1$aM15713r3Xsvxbi31lqr1Q$nFNh2CVHVjNldFVKDHDlm4CbdRSCdEBsjjJxD+iCs5E",
95
+ },
96
+ {
97
+ "name": "MD5 Crypt",
98
+ "regex": r"^\$1\$.{8}\$.{22}$",
99
+ "length": None,
100
+ "description": "MD5 Unix Crypt",
101
+ "example": "$1$O3JMY.Tw$AdLnLjQ/5jXF9.MTp3gHv/",
102
+ },
103
+ {
104
+ "name": "SHA-512 Crypt",
105
+ "regex": r"^\$6\$.{8,16}\$.{86}$",
106
+ "length": None,
107
+ "description": "SHA-512 Unix Crypt",
108
+ "example": "$6$52450745$k5ka2p8bFuSmoVT1tzOyyuaREkkKBcCNqoDKzYiJL9RaE8yMnPgh2XzzF0NDrUhgrcLwg78xs1w5pJiypEdFX/",
109
+ },
110
+ {
111
+ "name": "BLAKE2b",
112
+ "regex": r"^[a-f0-9]{128}$",
113
+ "length": 128,
114
+ "description": "BLAKE2b 512-bit",
115
+ "example": "786a02f742015903c6c6fd852552d272912f4740e15847618a86e217f71f5419d25e1031afee585313896444934eb04b903a685b1448b755d56f701afe9be2ce",
116
+ },
117
+ {
118
+ "name": "BLAKE2s",
119
+ "regex": r"^[a-f0-9]{64}$",
120
+ "length": 64,
121
+ "description": "BLAKE2s 256-bit",
122
+ "example": "606beeec743ccbeff6cbcdf5d5302aa855c256c29b88c8ed331ea1a6bf3c8812",
123
+ },
124
+ {
125
+ "name": "CRC32",
126
+ "regex": r"^[a-f0-9]{8}$",
127
+ "length": 8,
128
+ "description": "CRC32 cyclic redundancy check",
129
+ "example": "fc4f1d7f",
130
+ },
131
+ {
132
+ "name": "Adler32",
133
+ "regex": r"^[a-f0-9]{8}$",
134
+ "length": 8,
135
+ "description": "Adler-32 checksum",
136
+ "example": "03da018b",
137
+ },
138
+ {
139
+ "name": "RIPEMD-160",
140
+ "regex": r"^[a-f0-9]{40}$",
141
+ "length": 40,
142
+ "description": "RACE Integrity Primitives Evaluation MD 160-bit",
143
+ "example": "108f07b8382412612c048d07d13f814118445acd",
144
+ },
145
+ {
146
+ "name": "Whirlpool",
147
+ "regex": r"^[a-f0-9]{128}$",
148
+ "length": 128,
149
+ "description": "Whirlpool 512-bit hash",
150
+ "example": "19fa61d75522a4669b44e39c1d2e1726c530232130d407f89afee0964997f7a73e83be698b288febcf88e3e03c4f0757ea8964e59b63d93708b138cc42a66eb3",
151
+ },
152
+ {
153
+ "name": "LM Hash",
154
+ "regex": r"^[a-f0-9]{32}$",
155
+ "length": 32,
156
+ "description": "LAN Manager Hash (Windows legacy)",
157
+ "example": "e52cac67419a9a224a3b108f3fa6cb6d",
158
+ },
159
+ {
160
+ "name": "Tiger-192",
161
+ "regex": r"^[a-f0-9]{48}$",
162
+ "length": 48,
163
+ "description": "Tiger 192-bit",
164
+ "example": "9f00f599072300dd276abb38c8eb6dcae6f0f3b0ff6d0f29",
165
+ },
166
+ {
167
+ "name": "Haval-256",
168
+ "regex": r"^[a-f0-9]{64}$",
169
+ "length": 64,
170
+ "description": "Haval 256-bit",
171
+ "example": "4a665e3f9f31e52f42b08cde57a93b2cedc1b8a25a0a76a2a15abd76f5e0a48",
172
+ },
173
+ {
174
+ "name": "Base64",
175
+ "regex": r"^[A-Za-z0-9+/]+=*$",
176
+ "length": None,
177
+ "description": "Base64 encoded string",
178
+ "example": "aGVsbG8gd29ybGQ=",
179
+ },
180
+ ]
181
+
182
+ LENGTH_MAP = {
183
+ 8: ["CRC32", "Adler32"],
184
+ 32: ["MD5", "NTLM", "LM Hash"],
185
+ 40: ["SHA-1", "RIPEMD-160"],
186
+ 48: ["Tiger-192"],
187
+ 56: ["SHA-224"],
188
+ 64: ["SHA-256", "SHA3-256", "BLAKE2s", "Haval-256"],
189
+ 96: ["SHA-384"],
190
+ 128: ["SHA-512", "SHA3-512", "BLAKE2b", "Whirlpool"],
191
+ }
192
+
193
+
194
+ def detect_hash(hash_string: str) -> list[dict]:
195
+ """Return list of possible hash types for a given hash string."""
196
+ hash_string = hash_string.strip()
197
+ matches = []
198
+
199
+ for pattern in HASH_PATTERNS:
200
+ if re.match(pattern["regex"], hash_string, re.IGNORECASE):
201
+ matches.append(pattern)
202
+
203
+ # Deduplicate by name
204
+ seen = set()
205
+ unique = []
206
+ for m in matches:
207
+ if m["name"] not in seen:
208
+ seen.add(m["name"])
209
+ unique.append(m)
210
+
211
+ return unique
212
+
213
+
214
+ def is_hex(s: str) -> bool:
215
+ try:
216
+ int(s, 16)
217
+ return True
218
+ except ValueError:
219
+ return False
220
+
221
+
222
+ def classify_by_length(length: int) -> list[str]:
223
+ return LENGTH_MAP.get(length, [])
hashcrack/lookup.py ADDED
@@ -0,0 +1,163 @@
1
+ import hashlib
2
+ import requests
3
+ from typing import Optional
4
+
5
+ HEADERS = {"User-Agent": "hashcrack/0.1.0 (CTF hash lookup tool)"}
6
+ TIMEOUT = 10
7
+
8
+
9
+ def lookup_md5_online(hash_str: str) -> Optional[str]:
10
+ """Try multiple free hash lookup APIs for MD5/SHA1/SHA256."""
11
+ hash_str = hash_str.lower().strip()
12
+
13
+ # md5decrypt.net
14
+ try:
15
+ url = f"https://md5decrypt.net/Api/api.php?hash={hash_str}&hash_type=md5&email=test@test.com&code=code1"
16
+ resp = requests.get(url, timeout=TIMEOUT, headers=HEADERS)
17
+ if resp.ok and resp.text and resp.text not in ("", "ACCESS_DENIED", "INVALID_HASH", "NOT_FOUND"):
18
+ return resp.text.strip()
19
+ except Exception:
20
+ pass
21
+
22
+ return None
23
+
24
+
25
+ def lookup_nitrxgen(hash_str: str) -> Optional[str]:
26
+ """Use nitrxgen rainbow table lookup for MD5."""
27
+ hash_str = hash_str.lower().strip()
28
+ try:
29
+ url = f"https://www.nitrxgen.net/md5db/{hash_str}"
30
+ resp = requests.get(url, timeout=TIMEOUT, headers=HEADERS)
31
+ if resp.ok and resp.text.strip():
32
+ return resp.text.strip()
33
+ except Exception:
34
+ pass
35
+ return None
36
+
37
+
38
+ def lookup_hashes_com(hash_str: str) -> Optional[str]:
39
+ """Query hashes.com API."""
40
+ hash_str = hash_str.lower().strip()
41
+ try:
42
+ url = f"https://hashes.com/en/api/identify"
43
+ resp = requests.post(
44
+ url,
45
+ data={"hash": hash_str},
46
+ timeout=TIMEOUT,
47
+ headers=HEADERS,
48
+ )
49
+ if resp.ok:
50
+ data = resp.json()
51
+ if data.get("result"):
52
+ return data["result"]
53
+ except Exception:
54
+ pass
55
+ return None
56
+
57
+
58
+ def lookup_hashtoolkit(hash_str: str) -> Optional[str]:
59
+ """Query hashtoolkit.com."""
60
+ hash_str = hash_str.lower().strip()
61
+ try:
62
+ url = f"https://hashtoolkit.com/reverse-hash/?hash={hash_str}"
63
+ resp = requests.get(url, timeout=TIMEOUT, headers=HEADERS)
64
+ if resp.ok:
65
+ import re
66
+ match = re.search(r'<span class="hashValue">(.*?)</span>', resp.text)
67
+ if match:
68
+ return match.group(1).strip()
69
+ except Exception:
70
+ pass
71
+ return None
72
+
73
+
74
+ def crackstation_lookup(hash_str: str) -> Optional[str]:
75
+ """Query CrackStation via their API."""
76
+ hash_str = hash_str.lower().strip()
77
+ try:
78
+ url = "https://crackstation.net/crackstation-api/v1/hash.php"
79
+ resp = requests.post(
80
+ url,
81
+ data={"hash": hash_str, "submit": "Crack Hashes"},
82
+ timeout=TIMEOUT,
83
+ headers={**HEADERS, "Content-Type": "application/x-www-form-urlencoded"},
84
+ )
85
+ if resp.ok and resp.text:
86
+ import json
87
+ try:
88
+ results = json.loads(resp.text)
89
+ if isinstance(results, list) and results and results[0].get("cracked"):
90
+ return results[0].get("plaintext")
91
+ except Exception:
92
+ pass
93
+ except Exception:
94
+ pass
95
+ return None
96
+
97
+
98
+ def lookup_hash(hash_str: str, hash_type: Optional[str] = None) -> dict:
99
+ """
100
+ Attempt to look up plaintext for a hash using multiple public services.
101
+ Returns dict with 'found', 'plaintext', 'source' keys.
102
+ """
103
+ hash_str = hash_str.strip()
104
+ result = {"found": False, "plaintext": None, "source": None}
105
+
106
+ # Try multiple sources
107
+ sources = [
108
+ ("nitrxgen (MD5 rainbow tables)", lookup_nitrxgen),
109
+ ("hashes.com", lookup_hashes_com),
110
+ ("hashtoolkit.com", lookup_hashtoolkit),
111
+ ("crackstation.net", crackstation_lookup),
112
+ ]
113
+
114
+ for source_name, fn in sources:
115
+ try:
116
+ plaintext = fn(hash_str)
117
+ if plaintext:
118
+ result["found"] = True
119
+ result["plaintext"] = plaintext
120
+ result["source"] = source_name
121
+ return result
122
+ except Exception:
123
+ continue
124
+
125
+ return result
126
+
127
+
128
+ def generate_hash(text: str, algorithm: str) -> Optional[str]:
129
+ """Generate hash of given text using specified algorithm."""
130
+ algorithm = algorithm.lower().replace("-", "").replace("_", "")
131
+ algo_map = {
132
+ "md5": "md5",
133
+ "sha1": "sha1",
134
+ "sha224": "sha224",
135
+ "sha256": "sha256",
136
+ "sha384": "sha384",
137
+ "sha512": "sha512",
138
+ "sha3224": "sha3_224",
139
+ "sha3256": "sha3_256",
140
+ "sha3384": "sha3_384",
141
+ "sha3512": "sha3_512",
142
+ "blake2b": "blake2b",
143
+ "blake2s": "blake2s",
144
+ }
145
+
146
+ algo_key = algo_map.get(algorithm)
147
+ if not algo_key:
148
+ return None
149
+
150
+ try:
151
+ h = hashlib.new(algo_key)
152
+ h.update(text.encode("utf-8"))
153
+ return h.hexdigest()
154
+ except ValueError:
155
+ return None
156
+
157
+
158
+ def verify_hash(hash_str: str, plaintext: str, algorithm: str) -> bool:
159
+ """Verify that plaintext matches hash using given algorithm."""
160
+ computed = generate_hash(plaintext, algorithm)
161
+ if computed is None:
162
+ return False
163
+ return computed.lower() == hash_str.lower().strip()
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 shazeus
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,128 @@
1
+ Metadata-Version: 2.2
2
+ Name: hashcrack-cli
3
+ Version: 0.1.1
4
+ Summary: Hash identification and lookup tool for CTF and security research
5
+ Author-email: shazeus <efeborazan07@gmail.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/shazeus/hashcrack
8
+ Project-URL: Repository, https://github.com/shazeus/hashcrack
9
+ Project-URL: Bug Tracker, https://github.com/shazeus/hashcrack/issues
10
+ Keywords: hash,ctf,security,md5,sha256,cracking,lookup,cli,hashing,cryptography
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Environment :: Console
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Intended Audience :: Information Technology
15
+ Classifier: Operating System :: OS Independent
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.9
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Topic :: Security
22
+ Classifier: Topic :: Security :: Cryptography
23
+ Classifier: Topic :: Utilities
24
+ Requires-Python: >=3.9
25
+ Description-Content-Type: text/markdown
26
+ License-File: LICENSE
27
+ Requires-Dist: click>=8.0
28
+ Requires-Dist: rich>=13.0
29
+ Requires-Dist: requests>=2.28
30
+
31
+ <p align="center">
32
+ <h1 align="center">hashcrack</h1>
33
+ <p align="center">Hash identification and lookup tool for CTF and security research.</p>
34
+ <p align="center">
35
+ <a href="https://pypi.org/project/hashcrack-cli/"><img src="https://img.shields.io/pypi/v/hashcrack-cli?color=blue&style=flat-square" alt="PyPI"></a>
36
+ <a href="https://pypi.org/project/hashcrack-cli/"><img src="https://img.shields.io/pypi/pyversions/hashcrack-cli?style=flat-square" alt="Python"></a>
37
+ <a href="https://github.com/shazeus/hashcrack/blob/master/LICENSE"><img src="https://img.shields.io/badge/license-MIT-green?style=flat-square" alt="License"></a>
38
+ <a href="https://github.com/shazeus/hashcrack/stargazers"><img src="https://img.shields.io/github/stars/shazeus/hashcrack?style=flat-square" alt="Stars"></a>
39
+ </p>
40
+ </p>
41
+
42
+ ---
43
+
44
+ **hashcrack** is a fast, feature-rich CLI tool for hash analysis in CTF competitions and security research. It auto-detects hash types (MD5, SHA-1, SHA-256, bcrypt, NTLM, and 20+ more), queries public hash databases, performs wordlist attacks, generates hashes, and provides deep statistical analysis — all with beautiful terminal output powered by Rich.
45
+
46
+ - **Auto-detection** — identifies 20+ hash types from MD5 to Argon2, bcrypt, NTLM, and scrypt
47
+ - **Online lookup** — queries multiple public hash cracking APIs simultaneously
48
+ - **Wordlist attack** — crack hashes locally using any wordlist (e.g. rockyou.txt)
49
+ - **Hash generation** — compute MD5, SHA-1, SHA-256, SHA-512, BLAKE2 and more in one command
50
+ - **Batch mode** — process hundreds of hashes from a file with optional auto-lookup
51
+ - **Verification** — confirm plaintext/hash pairs across all supported algorithms
52
+ - **Deep analysis** — entropy calculation, charset analysis, and type classification
53
+ - **JSON output** — every command supports `--json` for scripting and piping
54
+
55
+ ## Installation
56
+
57
+ ```bash
58
+ pip install hashcrack-cli
59
+ ```
60
+
61
+ Or install from source:
62
+
63
+ ```bash
64
+ git clone https://github.com/shazeus/hashcrack
65
+ cd hashcrack
66
+ pip install -e .
67
+ ```
68
+
69
+ ## Usage
70
+
71
+ ```bash
72
+ # Identify a hash type
73
+ hashcrack identify 5d41402abc4b2a76b9719d911017c592
74
+
75
+ # Look up plaintext via public APIs
76
+ hashcrack lookup 5d41402abc4b2a76b9719d911017c592
77
+
78
+ # Generate hashes
79
+ hashcrack generate "hello world"
80
+ hashcrack generate "secret" --algo md5 --algo sha256
81
+
82
+ # Crack with a wordlist
83
+ hashcrack wordlist rockyou.txt 5d41402abc4b2a76b9719d911017c592
84
+
85
+ # Batch process hashes from a file
86
+ hashcrack batch hashes.txt --lookup
87
+
88
+ # Verify plaintext against hash
89
+ hashcrack verify 5d41402abc4b2a76b9719d911017c592 "hello"
90
+
91
+ # Deep analysis
92
+ hashcrack analyze 5d41402abc4b2a76b9719d911017c592
93
+
94
+ # List all supported hash types
95
+ hashcrack info
96
+ ```
97
+
98
+ ## Commands
99
+
100
+ | Command | Description |
101
+ |---------|-------------|
102
+ | `identify <hash>` | Auto-detect the type(s) of a hash |
103
+ | `lookup <hash>` | Query public databases for the plaintext |
104
+ | `generate <text>` | Generate hashes for a string (all algorithms or specific ones) |
105
+ | `wordlist <file> <hash>` | Crack a hash using a local wordlist |
106
+ | `batch <file>` | Process multiple hashes from a file |
107
+ | `verify <hash> <plaintext>` | Confirm a hash/plaintext pair |
108
+ | `analyze <hash>` | Full analysis: type + lookup + entropy + charset |
109
+ | `info [type]` | Show details for a hash type, or list all supported types |
110
+
111
+ ## Configuration
112
+
113
+ All commands support these global options:
114
+
115
+ | Flag | Description |
116
+ |------|-------------|
117
+ | `--json` | Output results as JSON for scripting |
118
+ | `-q / --quiet` | Minimal output (useful in scripts) |
119
+ | `-h / --help` | Show help for any command |
120
+ | `-V / --version` | Show version |
121
+
122
+ ## Supported Hash Types
123
+
124
+ MD5, SHA-1, SHA-224, SHA-256, SHA-384, SHA-512, SHA3-256, SHA3-512, NTLM, LM Hash, bcrypt, Argon2, scrypt, MD5 Crypt, SHA-512 Crypt, RIPEMD-160, BLAKE2b, BLAKE2s, CRC32, Adler32, Tiger-192, Haval-256, MySQL4/5, Whirlpool, Base64
125
+
126
+ ## License
127
+
128
+ MIT — see [LICENSE](LICENSE).
@@ -0,0 +1,11 @@
1
+ hashcrack/__init__.py,sha256=j8CLnPWX6MTBo9iUD2tq6PeAtuutXV9oYyiLl8TSHRA,45
2
+ hashcrack/__main__.py,sha256=2tXjfx7WOXfrjrcywu-MAx-ievt3WsEY9ARdB3Jhh5w,68
3
+ hashcrack/cli.py,sha256=SgN5lZ6WKPK96DH5IPLFryQ1Vgno2rRHz74w-EHcQrU,24980
4
+ hashcrack/detector.py,sha256=IFAWVV7WQXwYsrCYoT9o2dny8cbznu4nWrdcSWHlBOU,6947
5
+ hashcrack/lookup.py,sha256=wzSiuNA82va7gB_XUnI6gsQzurQxO4_txXjy3--A4tU,4998
6
+ hashcrack_cli-0.1.1.dist-info/LICENSE,sha256=HZHbJWXuWLEERO18ylRPsLaCFaWaJmc56UdeAaLJ7E8,1064
7
+ hashcrack_cli-0.1.1.dist-info/METADATA,sha256=v8R8USdTnaBVY9ZehNezdC9u6VhOgTUfZWH7y6f_O6Q,5128
8
+ hashcrack_cli-0.1.1.dist-info/WHEEL,sha256=beeZ86-EfXScwlR_HKu4SllMC9wUEj_8Z_4FJ3egI2w,91
9
+ hashcrack_cli-0.1.1.dist-info/entry_points.txt,sha256=Zhe4EhOuzYtMRXCwitA5F5avKfRYPBjPZ60O99JhVpE,48
10
+ hashcrack_cli-0.1.1.dist-info/top_level.txt,sha256=WAtfAMG7bU80JoUtZJU_12yeJC3a66sVKJEzM9di1xg,10
11
+ hashcrack_cli-0.1.1.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (76.1.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ hashcrack = hashcrack.cli:cli
@@ -0,0 +1 @@
1
+ hashcrack