DEV Community

Cover image for CryptoPals Crypto Challenges Using Rust: AES in ECB mode
Naveen âš¡
Naveen âš¡

Posted on • Updated on

CryptoPals Crypto Challenges Using Rust: AES in ECB mode

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

Context 💡

This challenge is intro to AES (Advanced Encryption Standard) encryption. AES is a block cipher, meaning the it encrypts message block by block, where block is fixed size length segments of the message to be encrypted. AES block sizes can be 128 bit, 192 bit, or 256 bit (which is also the key size) - which corresponds to multiple AES encryptions - AES-128, AES-192 and AES-256 respectively.

AES encrypts the message blocks through multiple mathematical operations & each block goes through multiple rounds with each round having it's own key. We're not going to go through all the individual complex operations AES performs, as it is beyond the scope of this post. However, I encourage you to go through this article to understand better before continuing.

ECB (Electronic CodeBook) mode is the simplest mode of operation of a block cipher. In ECB mode, each block of the message are simply encrypted seperately & encrypted blocks are concatenated to get cipher text. That's it. This is contrast to, say CBC mode, where each next block's encryption incorporates previously encrypted block.

The challenge gives us base64 encoded AES-128 encryption of some message & a key. We simply have to decrypt the message. Simple, we have the key after all.

Code 🕶

We're not going to re-invent the wheel by implementing each complex operation of AES as it'll go beyond the scope of our motive, but use a nice create - aes to do our AES encryption jobs.

First off, we read the base64 encoded file, removing any new-line chars, as bytes vec. Here's a utility function for that:

use base64;
use std::fs;

fn read_bytes(path: &str) -> Vec<u8> {
    let base64_s = fs::read_to_string(path)
        .and_then(|res| Ok(res.replace("\n", "")))
        .expect("Error reading file");
    base64::decode(base64_s).unwrap()
}
Enter fullscreen mode Exit fullscreen mode

Then we divide the read bytes vec in blocks of 16 bytes each. 16 bytes because AES-128 uses 16 byte blocks to encrypt data (128 bits = 16 bytes). Now, decrypt these blocks using aes crate utilities.

use aes::cipher::{generic_array::GenericArray, BlockDecrypt, NewBlockCipher};
use aes::Aes128;

pub fn decrypt_message(path: &str, key_str: &str) -> String {
    let base64_bytes = read_bytes(path);
    let key = GenericArray::clone_from_slice(key_str.as_bytes());

    // Construct blocks of 16 byte size for AES-128
    let mut blocks = Vec::new();
    (0..base64_bytes.len()).step_by(16).for_each(|x| {
        blocks.push(GenericArray::clone_from_slice(&base64_bytes[x..x + 16]));
    });

    // Initialize cipher
    let cipher = Aes128::new(&key);
    cipher.decrypt_blocks(&mut blocks);

    blocks.iter().flatten().map(|&x| x as char).collect()
}
Enter fullscreen mode Exit fullscreen mode

This is it, decrypted bytes are converted to text message lastly. Note that decrypted message contains some repeated non-english ASCII characters at the end. These are just padding characters appended before encryption to make the message size a multiple of 16, so that it can evenly be divided into 16-byte blocks.

Alright this is it!

See code on Github

Find me on:
Twitter - @heyNvN

naveeen.com

Top comments (0)