pkcrack 0.1.0

A Rust implementation of pkcrack - Known-plaintext attack against PkZip encryption
Documentation
//! Main command line interface for pkcrack

use clap::Parser;
use pkcrack::{Config, PkCracker, Result, Cli};
use std::process;
use std::time::Instant;

fn main() {
    let args = Cli::parse();

    if let Err(e) = run(args) {
        eprintln!("Error: {}", e);
        process::exit(1);
    }
}

fn run(args: Cli) -> Result<()> {
    let start_time = Instant::now();

    // Print banner
    print_banner();

    // Convert CLI to configuration
    let config = Config::try_from(args)?;

    // Validate configuration
    config.validate()?;

    // Print configuration summary
    print_config_summary(&config);

    // Create and run the cracker
    println!("\nInitializing pkcrack...");
    let mut cracker = PkCracker::new(config)?;

    println!("Starting attack...");
    let attack_start = Instant::now();

    // Execute the attack
    let result = cracker.attack()?;

    let attack_duration = attack_start.elapsed();

    // Print results
    print_results(&result, &cracker, attack_duration)?;

    // Generate and save report
    let report = cracker.generate_report(&result, &vec![], &vec![]); // Will be filled internally
    save_report_if_requested(&report)?;

    let total_duration = start_time.elapsed();
    println!("\nTotal execution time: {:?}", total_duration);

    Ok(())
}

fn print_banner() {
    println!("┌─────────────────────────────────────────────────────────────┐");
    println!("│                     PKCRACK v0.1.0                           │");
    println!("│          Rust Implementation of Known-Plaintext Attack        │");
    println!("│                 Against PkZip Encryption                      │");
    println!("│                    Original by Peter Conrad                   │");
    println!("│                   Rust Port by Claude Code                    │");
    println!("└─────────────────────────────────────────────────────────────┘");
    println!();
}

fn print_config_summary(config: &Config) {
    println!("=== CONFIGURATION ===");

    if let Some(ciphertext) = &config.ciphertext_path {
        println!("Ciphertext file: {}", ciphertext.display());
    } else if let Some(ciphertext_zip) = &config.ciphertext_zip {
        println!("Ciphertext ZIP: {}", ciphertext_zip.display());
    }

    if let Some(plaintext) = &config.plaintext_path {
        println!("Plaintext file: {}", plaintext.display());
    } else if let Some(plaintext_zip) = &config.plaintext_zip {
        println!("Plaintext ZIP: {}", plaintext_zip.display());
    }

    println!("Offset: {}", config.offset);
    println!("Case sensitive: {}", config.case_sensitive);
    println!("Abort on success: {}", config.abort_on_success);
    println!("No progress: {}", config.no_progress);

    if let Some(output) = &config.output_zip {
        println!("Output ZIP: {}", output.display());
    }

    println!();
}

fn print_results(result: &pkcrack::AttackResult, cracker: &PkCracker, duration: std::time::Duration) -> Result<()> {
    println!("\n=== ATTACK RESULTS ===");
    println!("Attack completed in: {:?}", duration);

    let stats = cracker.get_statistics();
    println!("Stage 1 generated {} key2 candidates", stats.stage1_candidates);
    println!("Stage 1 best offset: {}", stats.stage1_best_offset);
    println!("Stage 2 found {} solutions", stats.stage2_solutions);
    println!("Final result offset: {}", result.best_offset);

    println!("\n=== RECOVERED KEYS ===");
    println!("Key0: 0x{:08X}", result.keys.key0);
    println!("Key1: 0x{:08X}", result.keys.key1);
    println!("Key2: 0x{:08X}", result.keys.key2);

    if let Some(ref password) = result.password {
        println!("\n=== RECOVERED PASSWORD ===");
        println!("Password: {}", password);

        // Estimate password strength
        let password_strength = estimate_password_strength(password);
        println!("Password complexity: {:.1} bits", password_strength);
        println!("Password strength: {}", describe_password_strength(password_strength));
    } else {
        println!("\n=== NO PASSWORD FOUND ===");
        println!("Keys found but no password recovered.");
        println!("You can use the keys directly for decryption.");
    }

    println!("\n=== VALIDATION ===");
    println!("Solution validation: [Would require original data for validation]");

    Ok(())
}

fn estimate_password_strength(password: &str) -> f64 {
    let charset_size = estimate_charset_size(password);
    let length = password.len();
    (length as f64) * (charset_size as f64).log2()
}

fn estimate_charset_size(password: &str) -> usize {
    let mut has_lower = false;
    let mut has_upper = false;
    let mut has_digit = false;
    let mut has_special = false;

    for c in password.chars() {
        if c.is_lowercase() {
            has_lower = true;
        } else if c.is_uppercase() {
            has_upper = true;
        } else if c.is_ascii_digit() {
            has_digit = true;
        } else if !c.is_whitespace() {
            has_special = true;
        }
    }

    let mut size = 0;
    if has_lower { size += 26; }
    if has_upper { size += 26; }
    if has_digit { size += 10; }
    if has_special { size += 32; } // Approximate

    size.max(1)
}

fn describe_password_strength(complexity: f64) -> &'static str {
    match complexity as u32 {
        0..=28 => "Very Weak",
        29..=35 => "Weak",
        36..=59 => "Reasonable",
        60..=127 => "Strong",
        _ => "Very Strong",
    }
}

fn save_report_if_requested(report: &pkcrack::AttackReport) -> Result<()> {
    // Generate report filename with timestamp
    let timestamp = std::time::SystemTime::now()
        .duration_since(std::time::UNIX_EPOCH)
        .unwrap()
        .as_secs();

    let report_path = format!("pkcrack_report_{}.txt", timestamp);

    if let Err(e) = report.save_to_file(&report_path) {
        eprintln!("Warning: Failed to save report: {}", e);
    } else {
        println!("Detailed report saved to: {}", report_path);
    }

    Ok(())
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_password_strength_estimation() {
        assert_eq!(estimate_charset_size("abc"), 26); // lowercase only
        assert_eq!(estimate_charset_size("ABC"), 26); // uppercase only
        assert_eq!(estimate_charset_size("123"), 10); // digits only
        assert_eq!(estimate_charset_size("abc123"), 36); // lowercase + digits
        assert_eq!(estimate_charset_size("ABC123"), 36); // uppercase + digits
        assert_eq!(estimate_charset_size("abcABC"), 52); // lowercase + uppercase
        assert_eq!(estimate_charset_size("abcABC123"), 62); // lowercase + uppercase + digits
        assert!(estimate_charset_size("abc!@#") > 26); // includes special chars
    }

    #[test]
    fn test_password_strength_description() {
        assert_eq!(describe_password_strength(20.0), "Very Weak");
        assert_eq!(describe_password_strength(30.0), "Weak");
        assert_eq!(describe_password_strength(40.0), "Reasonable");
        assert_eq!(describe_password_strength(80.0), "Strong");
        assert_eq!(describe_password_strength(150.0), "Very Strong");
    }

    #[test]
    fn test_banner_printing() {
        // Just ensure it doesn't panic
        print_banner();
    }
}