hashcracker 0.1.0

GPU-accelerated password cracker — 41 hash types, Vulkan/Metal/DX12, single binary
use crate::hash_backend::{HashType, AttackMode, full_hash_slice};
use crate::potfile::Potfile;

pub fn run_prince_cpu(
    attack_mode: &AttackMode,
    hash_type: HashType,
    entries: &[(String, Vec<u8>)],
    potfile: &mut Potfile,
    active_session: &Option<crate::session::Session>,
) {
    let AttackMode::Prince { dict } = attack_mode else { return };
    eprintln!("Prince mode: {} words in dictionary", dict.len());

    let module = hash_type.module();
    let mut total_found: Vec<(usize, String, String)> = Vec::new();
    let n = dict.len();
    let dict = dict.clone();

    let mut sorted: Vec<&String> = dict.iter().collect();
    sorted.sort_by(|a, b| a.len().cmp(&b.len()).then(a.cmp(b)));
    let sorted: Vec<String> = sorted.into_iter().cloned().collect();

    for (idx, entry) in entries.iter().enumerate() {
        let parsed = match module.parse_hash_string(&entry.0) {
            Ok(p) => p,
            Err(e) => {
                eprintln!("Warning: failed to parse hash '{}': {}", entry.0, e);
                continue;
            }
        };

        let dw = module.digest_words() as usize;
        let full = full_hash_slice(&parsed, dw);
        let hash_slice = &full[..dw];
        let salt: &[u8] = if !parsed.salt.is_empty() {
            &parsed.salt
        } else {
            &entry.1
        };
        let mut found = false;

        let mut report = |pwd: &str| {
            println!("  ✅ {:<64} : {}", entry.0, pwd);
            potfile.record_crack(&entry.0, pwd);
            total_found.push((idx, entry.0.clone(), pwd.to_string()));
        };

        // Phase 1: single words
        for w in &sorted {
            if module.cpu_verify(w, &salt, hash_slice) {
                report(w);
                found = true;
                break;
            }
        }
        if found { continue; }

        // Phase 2: word pairs
        let pairs = n as u64 * n as u64;
        if pairs <= 10_000_000 {
            eprintln!("  Trying {} word pairs...", pairs);
            for a in &sorted {
                for b in &sorted {
                    let mut chain = a.clone();
                    chain.push_str(b);
                    if module.cpu_verify(&chain, &salt, hash_slice) {
                        report(&chain);
                        found = true;
                        break;
                    }
                }
                if found { break; }
            }
        }
        if found { continue; }

        // Phase 3: word triples
        let triples = n as u64 * n as u64 * n as u64;
        if triples <= 10_000_000 {
            eprintln!("  Trying {} word triples...", triples);
            for a in &sorted {
                for b in &sorted {
                    for c in &sorted {
                        let mut chain = a.clone();
                        chain.push_str(b);
                        chain.push_str(c);
                        if module.cpu_verify(&chain, &salt, hash_slice) {
                            report(&chain);
                            found = true;
                            break;
                        }
                    }
                    if found { break; }
                }
                if found { break; }
            }
        }

        if !found {
            eprintln!("  ❌ Not found for hash {}", entry.0);
        }
    }

    if !total_found.is_empty() {
        if let Err(e) = potfile.save() {
            eprintln!("Warning: failed to save potfile: {}", e);
        }
    }

    if let Some(ref sess) = active_session {
        let _ = sess.delete();
    }
}

fn single_crack_candidates(username: &str) -> Vec<String> {
    let mut candidates = Vec::new();
    let lower = username.to_lowercase();
    let upper = username.to_uppercase();
    let rev: String = username.chars().rev().collect();
    let cap = if let Some(c) = username.chars().next() {
        let mut s = c.to_uppercase().collect::<String>();
        s.extend(username.chars().skip(1).map(|c| c.to_lowercase().next().unwrap_or(c)));
        s
    } else {
        username.to_string()
    };

    candidates.push(username.to_string());
    if lower != *username { candidates.push(lower.clone()); }
    if upper != *username { candidates.push(upper.clone()); }
    if cap != *username && cap != lower && cap != upper { candidates.push(cap.clone()); }
    candidates.push(rev);
    candidates.push(format!("{}{}", username, username));

    for suffix in &["!", "123", "123!", "1", "!", "@", "#", "2024", "2025", "2026"] {
        candidates.push(format!("{}{}", username, suffix));
        if lower != *username { candidates.push(format!("{}{}", lower, suffix)); }
        if cap != *username { candidates.push(format!("{}{}", cap, suffix)); }
    }
    for prefix in &["!", "@", "#"] {
        candidates.push(format!("{}{}", prefix, username));
    }

    candidates
}

pub fn run_single_crack(
    hash_type: HashType,
    entries: &[(String, Option<String>, Vec<u8>)],
    usernames: &[String],
    potfile: &mut Potfile,
    active_session: &Option<crate::session::Session>,
) {
    let module = hash_type.module();
    eprintln!("Single crack mode: {} username(s), {} target hash(es)", usernames.len(), entries.len());
    let mut total_found: Vec<(usize, String, String)> = Vec::new();

    for (idx, entry) in entries.iter().enumerate() {
        let parsed = match module.parse_hash_string(&entry.0) {
            Ok(p) => p,
            Err(e) => {
                eprintln!("Warning: failed to parse hash '{}': {}", entry.0, e);
                continue;
            }
        };
        let dw = module.digest_words() as usize;
        let full = full_hash_slice(&parsed, dw);
        let hash_slice = &full[..dw];
        let salt: &[u8] = if !parsed.salt.is_empty() { &parsed.salt } else { &entry.2 };

        for user in usernames {
            let candidates = single_crack_candidates(user);
            for candidate in &candidates {
                if module.cpu_verify(candidate, salt, hash_slice) {
                    println!("  ✅ {:<64} : {}", entry.0, candidate);
                    potfile.record_crack(&entry.0, candidate);
                    total_found.push((idx, entry.0.clone(), candidate.to_string()));
                    return;
                }
            }
        }
        eprintln!("  ❌ Not found for hash {}", entry.0);
    }

    if !total_found.is_empty() {
        if let Err(e) = potfile.save() {
            eprintln!("Warning: failed to save potfile: {}", e);
        }
    }
    if let Some(ref sess) = active_session {
        let _ = sess.delete();
    }
}

pub fn run_markov_cpu(
    hash_type: HashType,
    entries: &[(String, Vec<u8>)],
    candidates: &[String],
    potfile: &mut Potfile,
    active_session: &Option<crate::session::Session>,
) {
    let module = hash_type.module();
    eprintln!("Markov CPU: verifying {} candidates against {} hash(es)", candidates.len(), entries.len());
    let mut total_found: Vec<(usize, String, String)> = Vec::new();

    for (idx, entry) in entries.iter().enumerate() {
        let parsed = match module.parse_hash_string(&entry.0) {
            Ok(p) => p,
            Err(e) => {
                eprintln!("Warning: failed to parse hash '{}': {}", entry.0, e);
                continue;
            }
        };
        let dw = module.digest_words() as usize;
        let full = full_hash_slice(&parsed, dw);
        let hash_slice = &full[..dw];
        let salt: &[u8] = if !parsed.salt.is_empty() { &parsed.salt } else { &entry.1 };

        for candidate in candidates {
            if module.cpu_verify(candidate, salt, hash_slice) {
                println!("  ✅ {:<64} : {}", entry.0, candidate);
                potfile.record_crack(&entry.0, candidate);
                total_found.push((idx, entry.0.clone(), candidate.to_string()));
                break;
            }
        }

        if total_found.iter().any(|(i, _, _)| *i == idx) {
            continue;
        }
        eprintln!("  ❌ Not found for hash {}", entry.0);
    }

    if !total_found.is_empty() {
        if let Err(e) = potfile.save() {
            eprintln!("Warning: failed to save potfile: {}", e);
        }
    }
    if let Some(ref sess) = active_session {
        let _ = sess.delete();
    }
}