# /// script
# requires-python = ">=3.10"
# dependencies = []
# ///
# Copyright (c) 2026 Aayush Sharma · SPDX-License-Identifier: MIT
# https://thedataengineering.com/lab-code/LICENSE.txt
"""Crack a hashed phone number in seconds.

Demonstrates how fast an unsalted hash lookup table can be built and used to crack a hashed phone number, and contrasts this with per-record salts."""

import argparse
import hashlib
import time
import random
import string
import json

def main():
    parser = argparse.ArgumentParser(description=__doc__.split('\n')[0])
    parser.add_argument("--digits", type=int, default=7, help="Number of digits in the phone numbers.")
    parser.add_argument("--seed", type=int, default=42, help="Random seed for reproducibility.")
    parser.add_argument("--json", type=str, help="Also write the results to this JSON file.")
    args = parser.parse_args()

    random.seed(args.seed)
    domain_size = 10 ** args.digits
    
    results = {"summary": {}, "tables": {"cracks": []}}

    # --- Part 1: Unsalted Hash Cracking ---
    print("--- Part 1: Unsalted Hashing ---")
    t0 = time.time()
    # Build dictionary from hash to input
    table = {hashlib.sha256(f"{n:0{args.digits}d}".encode()).hexdigest(): f"{n:0{args.digits}d}" for n in range(domain_size)}
    build_time = time.time() - t0
    print(f"Table of {domain_size} hashes built in {build_time:.2f} seconds.")

    # Pick a random target to crack
    target_number = f"{random.randint(0, domain_size - 1):0{args.digits}d}"
    target_hash = hashlib.sha256(target_number.encode()).hexdigest()

    t1 = time.time()
    cracked_number = table[target_hash]
    crack_time = time.time() - t1
    print(f"Cracked target hash {target_hash[:8]}... -> {cracked_number} in {crack_time:.5f} seconds.")

    results["summary"]["unsalted_build_time_s"] = build_time
    results["summary"]["unsalted_crack_time_s"] = crack_time
    results["tables"]["cracks"].append({
        "type": "unsalted", 
        "time_s": crack_time, 
        "success": cracked_number == target_number
    })

    # --- Part 2: Per-Record Salt ---
    print("\n--- Part 2: Per-Record Salt ---")
    num = f"{random.randint(0, domain_size - 1):0{args.digits}d}"
    salt = ''.join(random.choices(string.ascii_letters + string.digits, k=8))
    target_salted_hash = hashlib.sha256((salt + num).encode()).hexdigest()
    
    print(f"Target Record - Hash: {target_salted_hash[:8]}..., Salt: {salt}")

    t2 = time.time()
    salted_cracked = False
    salted_crack_time = 0
    for n in range(domain_size):
        guess = f"{n:0{args.digits}d}"
        if hashlib.sha256((salt + guess).encode()).hexdigest() == target_salted_hash:
            salted_crack_time = time.time() - t2
            print(f"Cracked salted hash -> {guess} in {salted_crack_time:.2f} seconds.")
            salted_cracked = True
            break
            
    results["summary"]["salted_crack_time_s"] = salted_crack_time
    results["tables"]["cracks"].append({
        "type": "per_record_salt", 
        "time_s": salted_crack_time, 
        "success": salted_cracked
    })

    # --- Output JSON ---
    if args.json:
        with open(args.json, "w") as f:
            json.dump(results, f, indent=2)

if __name__ == "__main__":
    main()