Autarch/modules/password_toolkit.py
DigiJ 2322f69516 v2.2.0 — Full arsenal expansion: 16 new security modules
Add WiFi Audit, API Fuzzer, Cloud Scanner, Threat Intel, Log Correlator,
Steganography, Anti-Forensics, BLE Scanner, Forensics, RFID/NFC, Malware
Sandbox, Password Toolkit, Web Scanner, Report Engine, Net Mapper, and
C2 Framework. Each module includes CLI interface, Flask routes, and web
UI template. Also includes Go DNS server source + binary, IP Capture
service, SYN Flood, Gone Fishing mail server, and hack hijack modules
from v2.0 work.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-03 05:20:39 -08:00

797 lines
34 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""AUTARCH Password Toolkit
Hash identification, cracking (hashcat/john integration), password generation,
credential spray/stuff testing, wordlist management, and password policy auditing.
"""
DESCRIPTION = "Password cracking & credential testing"
AUTHOR = "darkHal"
VERSION = "1.0"
CATEGORY = "analyze"
import os
import re
import json
import time
import string
import secrets
import hashlib
import threading
import subprocess
from pathlib import Path
from dataclasses import dataclass, field
from typing import Dict, List, Optional, Any, Tuple
try:
from core.paths import find_tool, get_data_dir
except ImportError:
import shutil
def find_tool(name):
return shutil.which(name)
def get_data_dir():
return str(Path(__file__).parent.parent / 'data')
# ── Hash Type Signatures ──────────────────────────────────────────────────────
@dataclass
class HashSignature:
name: str
regex: str
hashcat_mode: int
john_format: str
example: str
bits: int = 0
HASH_SIGNATURES: List[HashSignature] = [
HashSignature('MD5', r'^[a-fA-F0-9]{32}$', 0, 'raw-md5', 'd41d8cd98f00b204e9800998ecf8427e', 128),
HashSignature('SHA-1', r'^[a-fA-F0-9]{40}$', 100, 'raw-sha1', 'da39a3ee5e6b4b0d3255bfef95601890afd80709', 160),
HashSignature('SHA-224', r'^[a-fA-F0-9]{56}$', 1300, 'raw-sha224', 'd14a028c2a3a2bc9476102bb288234c415a2b01f828ea62ac5b3e42f', 224),
HashSignature('SHA-256', r'^[a-fA-F0-9]{64}$', 1400, 'raw-sha256', 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855', 256),
HashSignature('SHA-384', r'^[a-fA-F0-9]{96}$', 10800,'raw-sha384', '38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b', 384),
HashSignature('SHA-512', r'^[a-fA-F0-9]{128}$', 1700, 'raw-sha512', 'cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e', 512),
HashSignature('NTLM', r'^[a-fA-F0-9]{32}$', 1000, 'nt', '31d6cfe0d16ae931b73c59d7e0c089c0', 128),
HashSignature('LM', r'^[a-fA-F0-9]{32}$', 3000, 'lm', 'aad3b435b51404eeaad3b435b51404ee', 128),
HashSignature('bcrypt', r'^\$2[aby]?\$\d{1,2}\$[./A-Za-z0-9]{53}$', 3200, 'bcrypt', '$2b$12$LJ3m4ys3Lg2VBe5F.4oXzuLKmRPBRWvs5fS5K.zL1E8CfJzqS/VfO', 0),
HashSignature('scrypt', r'^\$7\$', 8900, 'scrypt', '', 0),
HashSignature('Argon2', r'^\$argon2(i|d|id)\$', 0, 'argon2', '', 0),
HashSignature('MySQL 4.1+', r'^\*[a-fA-F0-9]{40}$', 300, 'mysql-sha1', '*6C8989366EAF6BCBBAA855D6DA93DE65C96D33D9', 160),
HashSignature('SHA-512 Crypt', r'^\$6\$[./A-Za-z0-9]+\$[./A-Za-z0-9]{86}$', 1800, 'sha512crypt', '', 0),
HashSignature('SHA-256 Crypt', r'^\$5\$[./A-Za-z0-9]+\$[./A-Za-z0-9]{43}$', 7400, 'sha256crypt', '', 0),
HashSignature('MD5 Crypt', r'^\$1\$[./A-Za-z0-9]+\$[./A-Za-z0-9]{22}$', 500, 'md5crypt', '', 0),
HashSignature('DES Crypt', r'^[./A-Za-z0-9]{13}$', 1500, 'descrypt', '', 0),
HashSignature('APR1 MD5', r'^\$apr1\$', 1600, 'md5apr1', '', 0),
HashSignature('Cisco Type 5', r'^\$1\$[./A-Za-z0-9]{8}\$[./A-Za-z0-9]{22}$', 500, 'md5crypt', '', 0),
HashSignature('Cisco Type 7', r'^[0-9]{2}[0-9A-Fa-f]+$', 0, '', '', 0),
HashSignature('PBKDF2-SHA256', r'^\$pbkdf2-sha256\$', 10900,'pbkdf2-hmac-sha256', '', 0),
HashSignature('Django SHA256', r'^pbkdf2_sha256\$', 10000,'django', '', 0),
HashSignature('CRC32', r'^[a-fA-F0-9]{8}$', 0, '', 'deadbeef', 32),
]
# ── Password Toolkit Service ─────────────────────────────────────────────────
class PasswordToolkit:
"""Hash identification, cracking, generation, and credential testing."""
def __init__(self):
self._data_dir = os.path.join(get_data_dir(), 'password_toolkit')
self._wordlists_dir = os.path.join(self._data_dir, 'wordlists')
self._results_dir = os.path.join(self._data_dir, 'results')
os.makedirs(self._wordlists_dir, exist_ok=True)
os.makedirs(self._results_dir, exist_ok=True)
self._active_jobs: Dict[str, dict] = {}
# ── Hash Identification ───────────────────────────────────────────────
def identify_hash(self, hash_str: str) -> List[dict]:
"""Identify possible hash types for a given hash string."""
hash_str = hash_str.strip()
matches = []
for sig in HASH_SIGNATURES:
if re.match(sig.regex, hash_str):
matches.append({
'name': sig.name,
'hashcat_mode': sig.hashcat_mode,
'john_format': sig.john_format,
'bits': sig.bits,
'confidence': self._hash_confidence(hash_str, sig),
})
# Sort by confidence
matches.sort(key=lambda m: {'high': 0, 'medium': 1, 'low': 2}.get(m['confidence'], 3))
return matches
def _hash_confidence(self, hash_str: str, sig: HashSignature) -> str:
"""Estimate confidence of hash type match."""
# bcrypt, scrypt, argon2, crypt formats are definitive
if sig.name in ('bcrypt', 'scrypt', 'Argon2', 'SHA-512 Crypt',
'SHA-256 Crypt', 'MD5 Crypt', 'APR1 MD5',
'PBKDF2-SHA256', 'Django SHA256', 'MySQL 4.1+'):
return 'high'
# Length-based can be ambiguous (MD5 vs NTLM vs LM)
if len(hash_str) == 32:
return 'medium' # Could be MD5, NTLM, or LM
if len(hash_str) == 8:
return 'low' # CRC32 vs short hex
return 'medium'
def identify_batch(self, hashes: List[str]) -> List[dict]:
"""Identify types for multiple hashes."""
results = []
for h in hashes:
h = h.strip()
if not h:
continue
ids = self.identify_hash(h)
results.append({'hash': h, 'types': ids})
return results
# ── Hash Cracking ─────────────────────────────────────────────────────
def crack_hash(self, hash_str: str, hash_type: str = 'auto',
wordlist: str = '', attack_mode: str = 'dictionary',
rules: str = '', mask: str = '',
tool: str = 'auto') -> dict:
"""Start a hash cracking job.
attack_mode: 'dictionary', 'brute_force', 'mask', 'hybrid'
tool: 'hashcat', 'john', 'auto' (try hashcat first, then john)
"""
hash_str = hash_str.strip()
if not hash_str:
return {'ok': False, 'error': 'No hash provided'}
# Auto-detect hash type if needed
if hash_type == 'auto':
ids = self.identify_hash(hash_str)
if not ids:
return {'ok': False, 'error': 'Could not identify hash type'}
hash_type = ids[0]['name']
# Find cracking tool
hashcat = find_tool('hashcat')
john = find_tool('john')
if tool == 'auto':
tool = 'hashcat' if hashcat else ('john' if john else None)
elif tool == 'hashcat' and not hashcat:
return {'ok': False, 'error': 'hashcat not found'}
elif tool == 'john' and not john:
return {'ok': False, 'error': 'john not found'}
if not tool:
# Fallback: Python-based dictionary attack (slow but works)
return self._python_crack(hash_str, hash_type, wordlist)
# Default wordlist
if not wordlist:
wordlist = self._find_default_wordlist()
job_id = f'crack_{int(time.time())}_{secrets.token_hex(4)}'
if tool == 'hashcat':
return self._crack_hashcat(job_id, hash_str, hash_type,
wordlist, attack_mode, rules, mask)
else:
return self._crack_john(job_id, hash_str, hash_type,
wordlist, attack_mode, rules, mask)
def _crack_hashcat(self, job_id: str, hash_str: str, hash_type: str,
wordlist: str, attack_mode: str, rules: str,
mask: str) -> dict:
"""Crack using hashcat."""
hashcat = find_tool('hashcat')
# Get hashcat mode
mode = 0
for sig in HASH_SIGNATURES:
if sig.name == hash_type:
mode = sig.hashcat_mode
break
# Write hash to temp file
hash_file = os.path.join(self._results_dir, f'{job_id}.hash')
out_file = os.path.join(self._results_dir, f'{job_id}.pot')
with open(hash_file, 'w') as f:
f.write(hash_str + '\n')
cmd = [hashcat, '-m', str(mode), hash_file, '-o', out_file, '--potfile-disable']
attack_modes = {'dictionary': '0', 'brute_force': '3', 'mask': '3', 'hybrid': '6'}
cmd.extend(['-a', attack_modes.get(attack_mode, '0')])
if attack_mode in ('dictionary', 'hybrid') and wordlist:
cmd.append(wordlist)
if attack_mode in ('brute_force', 'mask') and mask:
cmd.append(mask)
elif attack_mode == 'brute_force' and not mask:
cmd.append('?a?a?a?a?a?a?a?a') # Default 8-char brute force
if rules:
cmd.extend(['-r', rules])
result_holder = {'result': None, 'done': False, 'process': None}
self._active_jobs[job_id] = result_holder
def run_crack():
try:
proc = subprocess.run(cmd, capture_output=True, text=True, timeout=3600)
result_holder['process'] = None
cracked = ''
if os.path.exists(out_file):
with open(out_file, 'r') as f:
cracked = f.read().strip()
result_holder['result'] = {
'ok': True,
'cracked': cracked,
'output': proc.stdout[-2000:] if proc.stdout else '',
'returncode': proc.returncode,
}
except subprocess.TimeoutExpired:
result_holder['result'] = {'ok': False, 'error': 'Crack timed out (1 hour)'}
except Exception as e:
result_holder['result'] = {'ok': False, 'error': str(e)}
finally:
result_holder['done'] = True
threading.Thread(target=run_crack, daemon=True).start()
return {'ok': True, 'job_id': job_id, 'message': f'Cracking started with hashcat (mode {mode})'}
def _crack_john(self, job_id: str, hash_str: str, hash_type: str,
wordlist: str, attack_mode: str, rules: str,
mask: str) -> dict:
"""Crack using John the Ripper."""
john = find_tool('john')
fmt = ''
for sig in HASH_SIGNATURES:
if sig.name == hash_type:
fmt = sig.john_format
break
hash_file = os.path.join(self._results_dir, f'{job_id}.hash')
with open(hash_file, 'w') as f:
f.write(hash_str + '\n')
cmd = [john, hash_file]
if fmt:
cmd.extend(['--format=' + fmt])
if wordlist and attack_mode == 'dictionary':
cmd.extend(['--wordlist=' + wordlist])
if rules:
cmd.extend(['--rules=' + rules])
if attack_mode in ('mask', 'brute_force') and mask:
cmd.extend(['--mask=' + mask])
result_holder = {'result': None, 'done': False}
self._active_jobs[job_id] = result_holder
def run_crack():
try:
proc = subprocess.run(cmd, capture_output=True, text=True, timeout=3600)
# Get cracked results
show = subprocess.run([john, '--show', hash_file],
capture_output=True, text=True, timeout=10)
result_holder['result'] = {
'ok': True,
'cracked': show.stdout.strip() if show.stdout else '',
'output': proc.stdout[-2000:] if proc.stdout else '',
'returncode': proc.returncode,
}
except subprocess.TimeoutExpired:
result_holder['result'] = {'ok': False, 'error': 'Crack timed out (1 hour)'}
except Exception as e:
result_holder['result'] = {'ok': False, 'error': str(e)}
finally:
result_holder['done'] = True
threading.Thread(target=run_crack, daemon=True).start()
return {'ok': True, 'job_id': job_id, 'message': f'Cracking started with john ({fmt or "auto"})'}
def _python_crack(self, hash_str: str, hash_type: str,
wordlist: str) -> dict:
"""Fallback pure-Python dictionary crack for common hash types."""
algo_map = {
'MD5': 'md5', 'SHA-1': 'sha1', 'SHA-256': 'sha256',
'SHA-512': 'sha512', 'SHA-224': 'sha224', 'SHA-384': 'sha384',
}
algo = algo_map.get(hash_type)
if not algo:
return {'ok': False, 'error': f'Python cracker does not support {hash_type}. Install hashcat or john.'}
if not wordlist:
wordlist = self._find_default_wordlist()
if not wordlist or not os.path.exists(wordlist):
return {'ok': False, 'error': 'No wordlist available'}
hash_lower = hash_str.lower()
tried = 0
try:
with open(wordlist, 'r', encoding='utf-8', errors='ignore') as f:
for line in f:
word = line.strip()
if not word:
continue
h = hashlib.new(algo, word.encode('utf-8')).hexdigest()
tried += 1
if h == hash_lower:
return {
'ok': True,
'cracked': f'{hash_str}:{word}',
'plaintext': word,
'tried': tried,
'message': f'Cracked! Password: {word}',
}
if tried >= 10_000_000:
break
except Exception as e:
return {'ok': False, 'error': str(e)}
return {'ok': True, 'cracked': '', 'tried': tried,
'message': f'Not cracked. Tried {tried:,} candidates.'}
def get_crack_status(self, job_id: str) -> dict:
"""Check status of a cracking job."""
holder = self._active_jobs.get(job_id)
if not holder:
return {'ok': False, 'error': 'Job not found'}
if not holder['done']:
return {'ok': True, 'done': False, 'message': 'Cracking in progress...'}
self._active_jobs.pop(job_id, None)
return {'ok': True, 'done': True, **holder['result']}
# ── Password Generation ───────────────────────────────────────────────
def generate_password(self, length: int = 16, count: int = 1,
uppercase: bool = True, lowercase: bool = True,
digits: bool = True, symbols: bool = True,
exclude_chars: str = '',
pattern: str = '') -> List[str]:
"""Generate secure random passwords."""
if pattern:
return [self._generate_from_pattern(pattern) for _ in range(count)]
charset = ''
if uppercase:
charset += string.ascii_uppercase
if lowercase:
charset += string.ascii_lowercase
if digits:
charset += string.digits
if symbols:
charset += '!@#$%^&*()-_=+[]{}|;:,.<>?'
if exclude_chars:
charset = ''.join(c for c in charset if c not in exclude_chars)
if not charset:
charset = string.ascii_letters + string.digits
length = max(4, min(length, 128))
count = max(1, min(count, 100))
passwords = []
for _ in range(count):
pw = ''.join(secrets.choice(charset) for _ in range(length))
passwords.append(pw)
return passwords
def _generate_from_pattern(self, pattern: str) -> str:
"""Generate password from pattern.
?u = uppercase, ?l = lowercase, ?d = digit, ?s = symbol, ?a = any
"""
result = []
i = 0
while i < len(pattern):
if pattern[i] == '?' and i + 1 < len(pattern):
c = pattern[i + 1]
if c == 'u':
result.append(secrets.choice(string.ascii_uppercase))
elif c == 'l':
result.append(secrets.choice(string.ascii_lowercase))
elif c == 'd':
result.append(secrets.choice(string.digits))
elif c == 's':
result.append(secrets.choice('!@#$%^&*()-_=+'))
elif c == 'a':
result.append(secrets.choice(
string.ascii_letters + string.digits + '!@#$%^&*'))
else:
result.append(pattern[i:i+2])
i += 2
else:
result.append(pattern[i])
i += 1
return ''.join(result)
# ── Password Policy Audit ─────────────────────────────────────────────
def audit_password(self, password: str) -> dict:
"""Audit a password against common policies and calculate entropy."""
import math
checks = {
'length_8': len(password) >= 8,
'length_12': len(password) >= 12,
'length_16': len(password) >= 16,
'has_uppercase': bool(re.search(r'[A-Z]', password)),
'has_lowercase': bool(re.search(r'[a-z]', password)),
'has_digit': bool(re.search(r'[0-9]', password)),
'has_symbol': bool(re.search(r'[^A-Za-z0-9]', password)),
'no_common_patterns': not self._has_common_patterns(password),
'no_sequential': not self._has_sequential(password),
'no_repeated': not self._has_repeated(password),
}
# Calculate entropy
charset_size = 0
if re.search(r'[a-z]', password):
charset_size += 26
if re.search(r'[A-Z]', password):
charset_size += 26
if re.search(r'[0-9]', password):
charset_size += 10
if re.search(r'[^A-Za-z0-9]', password):
charset_size += 32
entropy = len(password) * math.log2(charset_size) if charset_size > 0 else 0
# Strength rating
if entropy >= 80 and all(checks.values()):
strength = 'very_strong'
elif entropy >= 60 and checks['length_12']:
strength = 'strong'
elif entropy >= 40 and checks['length_8']:
strength = 'medium'
elif entropy >= 28:
strength = 'weak'
else:
strength = 'very_weak'
return {
'length': len(password),
'entropy': round(entropy, 1),
'strength': strength,
'checks': checks,
'charset_size': charset_size,
}
def _has_common_patterns(self, pw: str) -> bool:
common = ['password', '123456', 'qwerty', 'abc123', 'letmein',
'admin', 'welcome', 'monkey', 'dragon', 'master',
'login', 'princess', 'football', 'shadow', 'sunshine',
'trustno1', 'iloveyou', 'batman', 'access', 'hello']
pl = pw.lower()
return any(c in pl for c in common)
def _has_sequential(self, pw: str) -> bool:
for i in range(len(pw) - 2):
if (ord(pw[i]) + 1 == ord(pw[i+1]) == ord(pw[i+2]) - 1):
return True
return False
def _has_repeated(self, pw: str) -> bool:
for i in range(len(pw) - 2):
if pw[i] == pw[i+1] == pw[i+2]:
return True
return False
# ── Credential Spray / Stuff ──────────────────────────────────────────
def credential_spray(self, targets: List[dict], passwords: List[str],
protocol: str = 'ssh', threads: int = 4,
delay: float = 1.0) -> dict:
"""Spray passwords against target services.
targets: [{'host': '...', 'port': 22, 'username': 'admin'}, ...]
protocol: 'ssh', 'ftp', 'smb', 'http_basic', 'http_form'
"""
if not targets or not passwords:
return {'ok': False, 'error': 'Targets and passwords required'}
job_id = f'spray_{int(time.time())}_{secrets.token_hex(4)}'
result_holder = {
'done': False,
'results': [],
'total': len(targets) * len(passwords),
'tested': 0,
'found': [],
}
self._active_jobs[job_id] = result_holder
def do_spray():
import socket as sock_mod
for target in targets:
host = target.get('host', '')
port = target.get('port', 0)
username = target.get('username', '')
for pw in passwords:
if protocol == 'ssh':
ok = self._test_ssh(host, port or 22, username, pw)
elif protocol == 'ftp':
ok = self._test_ftp(host, port or 21, username, pw)
elif protocol == 'smb':
ok = self._test_smb(host, port or 445, username, pw)
else:
ok = False
result_holder['tested'] += 1
if ok:
cred = {'host': host, 'port': port, 'username': username,
'password': pw, 'protocol': protocol}
result_holder['found'].append(cred)
time.sleep(delay)
result_holder['done'] = True
threading.Thread(target=do_spray, daemon=True).start()
return {'ok': True, 'job_id': job_id,
'message': f'Spray started: {len(targets)} targets × {len(passwords)} passwords'}
def _test_ssh(self, host: str, port: int, user: str, pw: str) -> bool:
try:
import paramiko
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
client.connect(host, port=port, username=user, password=pw,
timeout=5, look_for_keys=False, allow_agent=False)
client.close()
return True
except Exception:
return False
def _test_ftp(self, host: str, port: int, user: str, pw: str) -> bool:
try:
import ftplib
ftp = ftplib.FTP()
ftp.connect(host, port, timeout=5)
ftp.login(user, pw)
ftp.quit()
return True
except Exception:
return False
def _test_smb(self, host: str, port: int, user: str, pw: str) -> bool:
try:
from impacket.smbconnection import SMBConnection
conn = SMBConnection(host, host, sess_port=port)
conn.login(user, pw)
conn.close()
return True
except Exception:
return False
def get_spray_status(self, job_id: str) -> dict:
holder = self._active_jobs.get(job_id)
if not holder:
return {'ok': False, 'error': 'Job not found'}
return {
'ok': True,
'done': holder['done'],
'tested': holder['tested'],
'total': holder['total'],
'found': holder['found'],
}
# ── Wordlist Management ───────────────────────────────────────────────
def list_wordlists(self) -> List[dict]:
"""List available wordlists."""
results = []
for f in Path(self._wordlists_dir).glob('*'):
if f.is_file():
size = f.stat().st_size
line_count = 0
try:
with open(f, 'r', encoding='utf-8', errors='ignore') as fh:
for _ in fh:
line_count += 1
if line_count > 10_000_000:
break
except Exception:
pass
results.append({
'name': f.name,
'path': str(f),
'size': size,
'size_human': self._human_size(size),
'lines': line_count,
})
# Also check common system locations
system_lists = [
'/usr/share/wordlists/rockyou.txt',
'/usr/share/seclists/Passwords/Common-Credentials/10-million-password-list-top-1000000.txt',
'/usr/share/wordlists/fasttrack.txt',
]
for path in system_lists:
if os.path.exists(path) and not any(r['path'] == path for r in results):
size = os.path.getsize(path)
results.append({
'name': os.path.basename(path),
'path': path,
'size': size,
'size_human': self._human_size(size),
'lines': -1, # Don't count for system lists
'system': True,
})
return results
def _find_default_wordlist(self) -> str:
"""Find the best available wordlist."""
# Check our wordlists dir first
for f in Path(self._wordlists_dir).glob('*'):
if f.is_file() and f.stat().st_size > 100:
return str(f)
# System locations
candidates = [
'/usr/share/wordlists/rockyou.txt',
'/usr/share/wordlists/fasttrack.txt',
'/usr/share/seclists/Passwords/Common-Credentials/10k-most-common.txt',
]
for c in candidates:
if os.path.exists(c):
return c
return ''
def upload_wordlist(self, filename: str, data: bytes) -> dict:
"""Save an uploaded wordlist."""
safe_name = re.sub(r'[^a-zA-Z0-9._-]', '_', filename)
path = os.path.join(self._wordlists_dir, safe_name)
with open(path, 'wb') as f:
f.write(data)
return {'ok': True, 'path': path, 'name': safe_name}
def delete_wordlist(self, name: str) -> dict:
path = os.path.join(self._wordlists_dir, name)
if os.path.exists(path):
os.remove(path)
return {'ok': True}
return {'ok': False, 'error': 'Wordlist not found'}
# ── Hash Generation (for testing) ─────────────────────────────────────
def hash_string(self, plaintext: str, algorithm: str = 'md5') -> dict:
"""Hash a string with a given algorithm."""
algo_map = {
'md5': hashlib.md5,
'sha1': hashlib.sha1,
'sha224': hashlib.sha224,
'sha256': hashlib.sha256,
'sha384': hashlib.sha384,
'sha512': hashlib.sha512,
}
fn = algo_map.get(algorithm.lower())
if not fn:
return {'ok': False, 'error': f'Unsupported algorithm: {algorithm}'}
h = fn(plaintext.encode('utf-8')).hexdigest()
return {'ok': True, 'hash': h, 'algorithm': algorithm, 'plaintext': plaintext}
# ── Tool Detection ────────────────────────────────────────────────────
def get_tools_status(self) -> dict:
"""Check which cracking tools are available."""
return {
'hashcat': bool(find_tool('hashcat')),
'john': bool(find_tool('john')),
'hydra': bool(find_tool('hydra')),
'ncrack': bool(find_tool('ncrack')),
}
@staticmethod
def _human_size(size: int) -> str:
for unit in ('B', 'KB', 'MB', 'GB'):
if size < 1024:
return f'{size:.1f} {unit}'
size /= 1024
return f'{size:.1f} TB'
# ── Singleton ─────────────────────────────────────────────────────────────────
_instance = None
_lock = threading.Lock()
def get_password_toolkit() -> PasswordToolkit:
global _instance
if _instance is None:
with _lock:
if _instance is None:
_instance = PasswordToolkit()
return _instance
# ── CLI ───────────────────────────────────────────────────────────────────────
def run():
"""Interactive CLI for Password Toolkit."""
svc = get_password_toolkit()
while True:
print("\n╔═══════════════════════════════════════╗")
print("║ PASSWORD TOOLKIT ║")
print("╠═══════════════════════════════════════╣")
print("║ 1 — Identify Hash ║")
print("║ 2 — Crack Hash ║")
print("║ 3 — Generate Passwords ║")
print("║ 4 — Audit Password Strength ║")
print("║ 5 — Hash a String ║")
print("║ 6 — Wordlist Management ║")
print("║ 7 — Tool Status ║")
print("║ 0 — Back ║")
print("╚═══════════════════════════════════════╝")
choice = input("\n Select: ").strip()
if choice == '0':
break
elif choice == '1':
h = input(" Hash: ").strip()
if not h:
continue
results = svc.identify_hash(h)
if results:
print(f"\n Possible types ({len(results)}):")
for r in results:
print(f" [{r['confidence'].upper():6s}] {r['name']}"
f" (hashcat: {r['hashcat_mode']}, john: {r['john_format']})")
else:
print(" No matching hash types found.")
elif choice == '2':
h = input(" Hash: ").strip()
wl = input(" Wordlist (empty=default): ").strip()
result = svc.crack_hash(h, wordlist=wl)
if result.get('job_id'):
print(f" {result['message']}")
print(" Waiting...")
while True:
time.sleep(2)
s = svc.get_crack_status(result['job_id'])
if s.get('done'):
if s.get('cracked'):
print(f"\n CRACKED: {s['cracked']}")
else:
print(f"\n Not cracked. {s.get('message', '')}")
break
elif result.get('cracked'):
print(f"\n CRACKED: {result['cracked']}")
else:
print(f" {result.get('message', result.get('error', ''))}")
elif choice == '3':
length = int(input(" Length (default 16): ").strip() or '16')
count = int(input(" Count (default 5): ").strip() or '5')
passwords = svc.generate_password(length=length, count=count)
print("\n Generated passwords:")
for pw in passwords:
audit = svc.audit_password(pw)
print(f" {pw} [{audit['strength']}] {audit['entropy']} bits")
elif choice == '4':
pw = input(" Password: ").strip()
if not pw:
continue
audit = svc.audit_password(pw)
print(f"\n Strength: {audit['strength']}")
print(f" Entropy: {audit['entropy']} bits")
print(f" Length: {audit['length']}")
print(f" Charset: {audit['charset_size']} characters")
for check, passed in audit['checks'].items():
mark = '\033[92m✓\033[0m' if passed else '\033[91m✗\033[0m'
print(f" {mark} {check}")
elif choice == '5':
text = input(" Plaintext: ").strip()
algo = input(" Algorithm (md5/sha1/sha256/sha512): ").strip() or 'sha256'
r = svc.hash_string(text, algo)
if r['ok']:
print(f" {r['algorithm']}: {r['hash']}")
else:
print(f" Error: {r['error']}")
elif choice == '6':
wls = svc.list_wordlists()
if wls:
print(f"\n Wordlists ({len(wls)}):")
for w in wls:
sys_tag = ' [system]' if w.get('system') else ''
print(f" {w['name']}{w['size_human']}{sys_tag}")
else:
print(" No wordlists found.")
elif choice == '7':
tools = svc.get_tools_status()
print("\n Tool Status:")
for tool, available in tools.items():
mark = '\033[92m✓\033[0m' if available else '\033[91m✗\033[0m'
print(f" {mark} {tool}")