hashcracker 1.1.3

GPU-accelerated password cracker — 42 hash types, Vulkan/Metal/DX12, single binary
pub struct RawSaltedSha1;
pub struct RawSaltedSha256;
pub struct RawSaltedSha512;

use crate::hashes::{AttackModeType, HashModule, HashPattern, ParsedHash};

fn parse_salted_hash(s: &str, hash_hex_len: usize) -> Result<(Vec<u8>, Vec<u8>), String> {
    let trimmed = s.trim();
    let sep = trimmed.find(':').ok_or_else(|| format!("Expected ':' separator in salted hash"))?;
    let hash_hex = &trimmed[..sep];
    let salt_raw = &trimmed[sep + 1..];
    if hash_hex.len() != hash_hex_len {
        return Err(format!("Expected {} hex chars for hash, got {}", hash_hex_len, hash_hex.len()));
    }
    if salt_raw.is_empty() || salt_raw.len() > 16 {
        return Err(format!("Salt must be 1-16 characters, got {}", salt_raw.len()));
    }
    let hash_bytes = hex::decode(hash_hex).map_err(|e| format!("Invalid hash hex: {}", e))?;
    let salt_bytes = salt_raw.as_bytes().to_vec();
    Ok((hash_bytes, salt_bytes))
}

impl HashModule for RawSaltedSha1 {
    fn name(&self) -> &'static str { "salted-sha1" }
    fn mode(&self) -> u32 { 111 }
    fn digest_words(&self) -> u32 { 5 }
    fn needs_int64(&self) -> bool { false }

    fn cpu_verify(&self, password: &str, salt: &[u8], hash: &[u32]) -> bool {
        use sha1::{Digest, Sha1};
        let mut ctx = Sha1::new();
        ctx.update(password.as_bytes());
        ctx.update(salt);
        let result = ctx.finalize();
        let mut words = [0u32; 5];
        for i in 0..5 {
            words[i] = u32::from_be_bytes(result[i * 4..i * 4 + 4].try_into().unwrap());
        }
        words == hash[..5]
    }

    fn shader_source(&self, mode: &AttackModeType) -> &'static str {
        match mode {
            AttackModeType::BruteForce => include_str!("../sha1_crack.wgsl"),
            AttackModeType::Mask => include_str!("../sha1_mask.wgsl"),
            AttackModeType::Wordlist => include_str!("../sha1_wordlist.wgsl"),
        }
    }

    fn detect_patterns(&self) -> &[HashPattern] {
        &[HashPattern { prefix: None, hex_len: Some(40), priority: 5 }]
    }

    fn parse_hash_string(&self, s: &str) -> Result<ParsedHash, String> {
        let (hash_bytes, salt_bytes) = parse_salted_hash(s, 40)?;
        let mut target = [0u32; 8];
        for i in 0..5 {
            target[i] = u32::from_be_bytes(hash_bytes[i * 4..i * 4 + 4].try_into().unwrap());
        }
        Ok(ParsedHash { hash_words: target, extra_words: [0u32; 8], salt: salt_bytes, digest_words: 5 })
    }
}

impl HashModule for RawSaltedSha256 {
    fn name(&self) -> &'static str { "salted-sha256" }
    fn mode(&self) -> u32 { 1410 }
    fn digest_words(&self) -> u32 { 8 }
    fn needs_int64(&self) -> bool { false }

    fn cpu_verify(&self, password: &str, salt: &[u8], hash: &[u32]) -> bool {
        use sha2::{Digest, Sha256};
        let mut ctx = Sha256::new();
        ctx.update(password.as_bytes());
        ctx.update(salt);
        let result = ctx.finalize();
        let mut words = [0u32; 8];
        for i in 0..8 {
            words[i] = u32::from_be_bytes(result[i * 4..i * 4 + 4].try_into().unwrap());
        }
        words == hash[..8]
    }

    fn shader_source(&self, mode: &AttackModeType) -> &'static str {
        match mode {
            AttackModeType::BruteForce => include_str!("../sha256_crack.wgsl"),
            AttackModeType::Mask => include_str!("../sha256_mask.wgsl"),
            AttackModeType::Wordlist => include_str!("../sha256_wordlist.wgsl"),
        }
    }

    fn detect_patterns(&self) -> &[HashPattern] {
        &[HashPattern { prefix: None, hex_len: Some(64), priority: 5 }]
    }

    fn parse_hash_string(&self, s: &str) -> Result<ParsedHash, String> {
        let (hash_bytes, salt_bytes) = parse_salted_hash(s, 64)?;
        let mut target = [0u32; 8];
        for i in 0..8 {
            target[i] = u32::from_be_bytes(hash_bytes[i * 4..i * 4 + 4].try_into().unwrap());
        }
        Ok(ParsedHash { hash_words: target, extra_words: [0u32; 8], salt: salt_bytes, digest_words: 8 })
    }
}

impl HashModule for RawSaltedSha512 {
    fn name(&self) -> &'static str { "salted-sha512" }
    fn mode(&self) -> u32 { 1710 }
    fn digest_words(&self) -> u32 { 16 }
    fn needs_int64(&self) -> bool { true }

    fn cpu_verify(&self, password: &str, salt: &[u8], hash: &[u32]) -> bool {
        use sha2::{Digest, Sha512};
        let mut ctx = Sha512::new();
        ctx.update(password.as_bytes());
        ctx.update(salt);
        let result = ctx.finalize();
        let mut words = [0u32; 16];
        for i in 0..8 {
            let word = u64::from_be_bytes(result[i * 8..i * 8 + 8].try_into().unwrap());
            words[i] = word as u32;
            words[i + 8] = (word >> 32) as u32;
        }
        words == hash[..16]
    }

    fn shader_source(&self, mode: &AttackModeType) -> &'static str {
        match mode {
            AttackModeType::BruteForce => include_str!("../sha512_crack.wgsl"),
            AttackModeType::Mask => include_str!("../sha512_mask.wgsl"),
            AttackModeType::Wordlist => include_str!("../sha512_wordlist.wgsl"),
        }
    }

    fn detect_patterns(&self) -> &[HashPattern] {
        &[HashPattern { prefix: None, hex_len: Some(128), priority: 5 }]
    }

    fn parse_hash_string(&self, s: &str) -> Result<ParsedHash, String> {
        let (hash_bytes, salt_bytes) = parse_salted_hash(s, 128)?;
        let mut target = [0u32; 8];
        let mut extra = [0u32; 8];
        for i in 0..8 {
            let word = u64::from_be_bytes(hash_bytes[i * 8..i * 8 + 8].try_into().unwrap());
            target[i] = word as u32;
            extra[i] = (word >> 32) as u32;
        }
        Ok(ParsedHash { hash_words: target, extra_words: extra, salt: salt_bytes, digest_words: 16 })
    }
}