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();
let config = Config::try_from(args)?;
config.validate()?;
print_config_summary(&config);
println!("\nInitializing pkcrack...");
let mut cracker = PkCracker::new(config)?;
println!("Starting attack...");
let attack_start = Instant::now();
let result = cracker.attack()?;
let attack_duration = attack_start.elapsed();
print_results(&result, &cracker, attack_duration)?;
let report = cracker.generate_report(&result, &vec![], &vec![]); 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);
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; }
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<()> {
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); assert_eq!(estimate_charset_size("ABC"), 26); assert_eq!(estimate_charset_size("123"), 10); assert_eq!(estimate_charset_size("abc123"), 36); assert_eq!(estimate_charset_size("ABC123"), 36); assert_eq!(estimate_charset_size("abcABC"), 52); assert_eq!(estimate_charset_size("abcABC123"), 62); assert!(estimate_charset_size("abc!@#") > 26); }
#[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() {
print_banner();
}
}