CryptoPals Crypto Challenges Using Rust: Implement CBC Mode
Tags: cryptography, rust, security, encryption
Published: 2022-02-04 16:02:41


This is [Challenge 10](https://cryptopals.com/sets/2/challenges/10) of [Cryptopals](https://cryptopals.com) challenges implemented using Rust language.

## Context 💡
This asks us to implement CBC (cipher block chaining) mode of AES-128 encryption. Straightforward. Highly recommend checking out [Challenge 7](https://dev.to/thenvn/cryptopals-crypto-challenges-using-rust-aes-in-ecb-mode-9bl) and [Challenge 9](https://dev.to/thenvn/cryptopals-crypto-challenges-using-rust-implement-pkcs7-padding-gfh), if you haven't yet.

Like ECB mode, CBC is also a block cipher mode. It also encrypts blocks of fixed size of the plaintext with necessary paddings. The difference from ECB comes from the fact that before each block is encrypted with AES-128, it is XORed with the encrypted block (or Initialization Vector, IV for first block) that came before it. So, in a sense, each block is mixed with previous cipher block and then encrypted. Like shown in the following figure:
<img src="https://upload.wikimedia.org/wikipedia/commons/thumb/8/80/CBC_encryption.svg/600px-CBC_encryption.svg.png" alt="cbc mode">
_(Image source: Wikipedia)_

Given key and IV, decryption is simply reverse of it. Each block is XORed with previous encrypted block (or IV) after being decrypted using AES.
<img src="https://upload.wikimedia.org/wikipedia/commons/thumb/2/2a/CBC_decryption.svg/600px-CBC_decryption.svg.png" alt="cbc mode">
_(Image source: Wikipedia)_

## Code 🕶
This is going to use `aes` crate for AES-128 encryptions and our pkcs#7 padding implementation from [Challenge 9](https://dev.to/thenvn/cryptopals-crypto-challenges-using-rust-implement-pkcs7-padding-gfh).

I'm going to create a quick utility function to xor two slices of bytes:
```rust
pub fn xor_bytes(bytes1: &[u8], bytes2: &[u8]) -> Vec<u8> {
    bytes1
        .iter()
        .zip(bytes2.iter())
        .map(|(&b1, &b2)| b1 ^ b2)
        .collect()
}
```

The encryption function is straightforward too. It will take message, key and IV as params. Since, we're implementing AES-128, block size is 16. So, encryption will be processed 16 bytes block at a time. `step_by()` method of `iterator`s is available for this purpose. After each block is encrypted and collected in a `Vec` it is hex encoded using `hex` crate.
```rust
use crate::set_2_block_crypto::c9_implement_pkcs_padding::pad_pkcs7;
use crate::utils::bitwise::xor_bytes;
use aes::cipher::{generic_array::GenericArray, BlockDecrypt, BlockEncrypt, NewBlockCipher};
use aes::Aes128;

pub fn aes_128_cbc_encrypt(message: &str, key_str: &str, iv_str: &str) -> String {
    // Normalize message by pkcs7 padding
    let padded_message = pad_pkcs7(message, 16);
    let msg_bytes = padded_message.as_bytes();
    let iv = iv_str.as_bytes().to_vec();

    let key = GenericArray::clone_from_slice(key_str.as_bytes());
    let cipher = Aes128::new(&key);

    let mut encrypted_blocks: Vec<Vec<u8>> = Vec::new();
    (0..message.len()).step_by(16).for_each(|x| {
        // Take last encrypted block or IV for first block iteration
        let last = encrypted_blocks.last().unwrap_or(&iv);

        // XOR last encrypted block with current msg block & encrypt result
        let xor_block = xor_bytes(last, &msg_bytes[x..x + 16]);
        let mut block = GenericArray::clone_from_slice(&xor_block);
        cipher.encrypt_block(&mut block);

        encrypted_blocks.push(block.into_iter().collect::<Vec<u8>>());
    });

    hex::encode(encrypted_blocks.into_iter().flatten().collect::<Vec<u8>>())
}
```

The decryption process is easy to follow too. Except, after being decrypted last byte is read to determine padding that was applied. And remove it to yield original message.
```rust
pub fn aes_128_cbc_decrypt(cipher_hex: &str, key_str: &str, iv_str: &str) -> String {
    let encrypted_bytes = hex::decode(cipher_hex).unwrap();
    let key = GenericArray::clone_from_slice(key_str.as_bytes());
    let iv = iv_str.as_bytes();
    let cipher = Aes128::new(&key);

    let mut decrypted_blocks: Vec<Vec<u8>> = Vec::new();
    (0..encrypted_bytes.len()).step_by(16).for_each(|x| {
        // Take last of encrypted block or IV in case of first block iteration
        let last = if x == 0 {
            &iv
        } else {
            &encrypted_bytes[x - 16..x]
        };

        // Decrypt AES
        let mut block = GenericArray::clone_from_slice(&encrypted_bytes[x..x + 16]);
        cipher.decrypt_block(&mut block);
        let decrypted_block = block.into_iter().collect::<Vec<u8>>();

        // XOR decrypted block with last encrypted block to undo xor during encryption
        let xor_block = xor_bytes(last, &decrypted_block);
        decrypted_blocks.push(xor_block);
    });

    // Get number of padding bytes applied during encryption & remove padding
    let padding_byte = *decrypted_blocks.last().unwrap().last().unwrap() as usize;
    decrypted_blocks
        .into_iter()
        .flatten()
        .take(encrypted_bytes.len() - padding_byte)
        .map(|x| x as char)
        .collect::<String>()
}
```

This was a quick & simple implementation for learning purpose. Serious implementations would enforce additional checks like validation of padding applied. And way more efficient operation like parallel decryption of blocks.

See code on [GitHub](https://github.com/theNvN/cryptopals-crypto-challenges/blob/master/src/set_2_block_crypto/c10_implement_cbc_mode.rs)

Find me on:

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

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