DEV Community

Cover image for CryptoPals Crypto Challenges Using Rust: Detect single-character XOR
Naveen âš¡
Naveen âš¡

Posted on

CryptoPals Crypto Challenges Using Rust: Detect single-character XOR

This is Challenge 4 of Cryptopals challenges implemented in Rust language.

Context 💡

This challenge is same as Challenge 3, except that instead of giving a single cipher text string, we're given many cipher strings (per line in given challenge file). One of which has been encrypted by single-character XOR. We have to find which one & decrypt the message.

I'd highly recommend checking out Challenge 3 first, if you haven't yet.

You might've guessed what we're going to do. We'll do exactly same as previous challenge, by trying to decrypt the cipher text by brute-forcing with every char, till we find one with best looking letter frequency score, except we're going to do it with every cipher text line. However in this case we'll be accumulating best score across all cipher texts. Only the message which was actually encrypted by single-char XOR would make sense as English text after decryption. So, it should yield best score.

Crack 🕶

Code would be very identical to previous challenge except we added another loop for going through cipher text line in given input file.

use hex;
use std::fs::File;
use std::io::{BufRead, BufReader};

pub fn detect_message(path: &str) -> String {
    let mut message = String::new();
    let mut best_score: f64 = f64::MIN;

    let file = File::open(path).expect("Error reading file!");
    let lines = BufReader::new(file).lines();

    let mut line: String;
    let mut key: u16;
    for line_result in lines {
        line = line_result.unwrap();
        for c in 0..255 {
            key = c as u16;

            let msg_bytes: Vec<u16> = hex::decode(&line.trim())
                .unwrap()
                .iter()
                .map(|&b| (b as u16) ^ key)
                .collect();

            let msg = String::from_utf16(&msg_bytes).unwrap();
            let score = calc_letter_freq_score(&msg);

            if score > best_score {
                best_score = score;
                message = String::from(msg);
            }
        }
    }

    message
}
Enter fullscreen mode Exit fullscreen mode

Note that we're using same letter frequency score calculation function calc_letter_freq_score() from previous challenge:

const LETTER_FREQ: [f64; 27] = [
    0.08167, 0.01492, 0.02782, 0.04253, 0.12702, 0.02228, 0.02015, // A-G
    0.06094, 0.06966, 0.00153, 0.00772, 0.04025, 0.02406, 0.06749, // H-N
    0.07507, 0.01929, 0.00095, 0.05987, 0.06327, 0.09056, 0.02758, // O-U
    0.00978, 0.02360, 0.00150, 0.01974, 0.00074, 0.19181, // V-Z & space char
];

pub fn calc_letter_freq_score(s: &str) -> f64 {
    let mut counts = vec![0_u32; 27];
    let mut score: f64 = 0_f64;

    s.chars().for_each(|c| match c {
        'a'..='z' => {
            counts[c as usize - 97] += 1;
        }
        'A'..='Z' => {
            counts[c as usize - 65] += 1;
        }
        ' ' => counts[26] += 1,
        _ => {}
    });

    for i in 0..27 {
        score += (counts[i] as f64) * LETTER_FREQ[i];
    }

    score
}
Enter fullscreen mode Exit fullscreen mode

And we've cracked the message yet again! 😎

See the code on Github

Find me on:
Twitter - @heyNvN

naveeen.com

Top comments (0)