CryptoPals Crypto Challenges Using Rust: Detect single-character XOR
Tags: rust, programming, cryptography
Published: 2022-01-01 14:20:11

This is [Challenge 4](https://cryptopals.com/sets/1/challenges/4) of [Cryptopals](https://cryptopals.com) challenges implemented in Rust language.

## Context 💡
This challenge is same as [Challenge 3](https://dev.to/thenvn/cryptopals-crypto-challenges-using-rust-single-byte-xor-cipher-1akg), except that instead of giving a single cipher text string, we're given many cipher strings (per line in given challenge [file](https://cryptopals.com/static/challenge-data/4.txt)). 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](https://dev.to/thenvn/cryptopals-crypto-challenges-using-rust-single-byte-xor-cipher-1akg) 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.

```rust
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
}
```

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

```rust
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
}
```

And we've cracked the message yet again! 😎

See the code on [Github](https://github.com/theNvN/cryptopals-crypto-challenges/blob/master/src/set_1_basics/c4_detect_single_char_xor.rs)

Find me on:
Twitter - [@heyNvN](https://twitter.com/heyNvN)

[naveeen.com](https://naveeen.com)