happy-cracking 0.5.0

A fast, comprehensive CTF toolkit for cryptographic encoding/decoding, classic ciphers, hash operations, and analysis tools
Documentation
use anyhow::{Context, Result};
use clap::Subcommand;

use des::cipher::{Array, BlockCipherDecrypt, BlockCipherEncrypt, KeyInit};
use des::{Des, TdesEde3};

#[derive(Subcommand)]
pub enum DesCipherAction {
    #[command(about = "DES ECB encrypt (hex key 8 bytes, hex plaintext)")]
    Encrypt {
        #[arg(help = "Plaintext (hex string, must be multiple of 8 bytes)")]
        input: String,
        #[arg(short, long, help = "Key (hex string, 8 bytes / 16 hex chars)")]
        key: String,
    },
    #[command(about = "DES ECB decrypt (hex key 8 bytes, hex ciphertext)")]
    Decrypt {
        #[arg(help = "Ciphertext (hex string, must be multiple of 8 bytes)")]
        input: String,
        #[arg(short, long, help = "Key (hex string, 8 bytes / 16 hex chars)")]
        key: String,
    },
    #[command(about = "Triple-DES (EDE3) encrypt (hex key 24 bytes, hex plaintext)")]
    TdesEncrypt {
        #[arg(help = "Plaintext (hex string, must be multiple of 8 bytes)")]
        input: String,
        #[arg(short, long, help = "Key (hex string, 24 bytes / 48 hex chars)")]
        key: String,
    },
    #[command(about = "Triple-DES (EDE3) decrypt (hex key 24 bytes, hex ciphertext)")]
    TdesDecrypt {
        #[arg(help = "Ciphertext (hex string, must be multiple of 8 bytes)")]
        input: String,
        #[arg(short, long, help = "Key (hex string, 24 bytes / 48 hex chars)")]
        key: String,
    },
}

pub fn run(action: DesCipherAction) -> Result<()> {
    match action {
        DesCipherAction::Encrypt { input, key } => {
            println!("{}", des_encrypt(&input, &key)?);
        }
        DesCipherAction::Decrypt { input, key } => {
            println!("{}", des_decrypt(&input, &key)?);
        }
        DesCipherAction::TdesEncrypt { input, key } => {
            println!("{}", tdes_encrypt(&input, &key)?);
        }
        DesCipherAction::TdesDecrypt { input, key } => {
            println!("{}", tdes_decrypt(&input, &key)?);
        }
    }
    Ok(())
}

fn parse_des_blocks(hex_input: &str) -> Result<Vec<u8>> {
    let bytes = hex::decode(hex_input.trim()).context("Failed to decode hex input")?;
    if bytes.is_empty() {
        anyhow::bail!("Input must not be empty");
    }
    if bytes.len() % 8 != 0 {
        anyhow::bail!(
            "Input length must be a multiple of 8 bytes, got {} bytes",
            bytes.len()
        );
    }
    Ok(bytes)
}

pub fn des_encrypt(hex_input: &str, hex_key: &str) -> Result<String> {
    let key_bytes = hex::decode(hex_key.trim()).context("Failed to decode hex key")?;
    if key_bytes.len() != 8 {
        anyhow::bail!(
            "DES key must be exactly 8 bytes (16 hex chars), got {} bytes",
            key_bytes.len()
        );
    }
    let plaintext = parse_des_blocks(hex_input)?;
    let cipher =
        Des::new(&Array::try_from(key_bytes.as_slice()).context("DES key must be 8 bytes")?);

    let mut output = Vec::with_capacity(plaintext.len());
    for block in plaintext.chunks(8) {
        let mut block_array = Array::try_from(block).context("DES block must be 8 bytes")?;
        cipher.encrypt_block(&mut block_array);
        output.extend_from_slice(&block_array);
    }
    Ok(hex::encode(output))
}

pub fn des_decrypt(hex_input: &str, hex_key: &str) -> Result<String> {
    let key_bytes = hex::decode(hex_key.trim()).context("Failed to decode hex key")?;
    if key_bytes.len() != 8 {
        anyhow::bail!(
            "DES key must be exactly 8 bytes (16 hex chars), got {} bytes",
            key_bytes.len()
        );
    }
    let ciphertext = parse_des_blocks(hex_input)?;
    let cipher =
        Des::new(&Array::try_from(key_bytes.as_slice()).context("DES key must be 8 bytes")?);

    let mut output = Vec::with_capacity(ciphertext.len());
    for block in ciphertext.chunks(8) {
        let mut block_array = Array::try_from(block).context("DES block must be 8 bytes")?;
        cipher.decrypt_block(&mut block_array);
        output.extend_from_slice(&block_array);
    }
    Ok(hex::encode(output))
}

pub fn tdes_encrypt(hex_input: &str, hex_key: &str) -> Result<String> {
    let key_bytes = hex::decode(hex_key.trim()).context("Failed to decode hex key")?;
    if key_bytes.len() != 24 {
        anyhow::bail!(
            "Triple-DES key must be exactly 24 bytes (48 hex chars), got {} bytes",
            key_bytes.len()
        );
    }
    let plaintext = parse_des_blocks(hex_input)?;
    let cipher = TdesEde3::new(
        &Array::try_from(key_bytes.as_slice()).context("Triple-DES key must be 24 bytes")?,
    );

    let mut output = Vec::with_capacity(plaintext.len());
    for block in plaintext.chunks(8) {
        let mut block_array = Array::try_from(block).context("DES block must be 8 bytes")?;
        cipher.encrypt_block(&mut block_array);
        output.extend_from_slice(&block_array);
    }
    Ok(hex::encode(output))
}

pub fn tdes_decrypt(hex_input: &str, hex_key: &str) -> Result<String> {
    let key_bytes = hex::decode(hex_key.trim()).context("Failed to decode hex key")?;
    if key_bytes.len() != 24 {
        anyhow::bail!(
            "Triple-DES key must be exactly 24 bytes (48 hex chars), got {} bytes",
            key_bytes.len()
        );
    }
    let ciphertext = parse_des_blocks(hex_input)?;
    let cipher = TdesEde3::new(
        &Array::try_from(key_bytes.as_slice()).context("Triple-DES key must be 24 bytes")?,
    );

    let mut output = Vec::with_capacity(ciphertext.len());
    for block in ciphertext.chunks(8) {
        let mut block_array = Array::try_from(block).context("DES block must be 8 bytes")?;
        cipher.decrypt_block(&mut block_array);
        output.extend_from_slice(&block_array);
    }
    Ok(hex::encode(output))
}