~shreyasminocha/cryptopals

7f538ec21fdc2d227e8ab2aabf7dec47d367dd15 — Shreyas Minocha 11 months ago fe79d58
Complete challenge 16
4 files changed, 93 insertions(+), 11 deletions(-)

M examples/09.rs
M examples/15.rs
A examples/16.rs
M src/aes.rs
M examples/09.rs => examples/09.rs +2 -2
@@ 5,12 5,12 @@ fn main() -> Result<(), Box<dyn std::error::Error>> {
#[cfg(test)]
mod tests {
    use super::*;
    use cryptopals::aes::pkcs7;
    use cryptopals::aes::pkcs7_pad;

    #[test]
    fn given_example() {
        assert_eq!(
            pkcs7(b"YELLOW SUBMARINE".to_vec(), 20),
            pkcs7_pad(b"YELLOW SUBMARINE".to_vec(), 20),
            b"YELLOW SUBMARINE\x04\x04\x04\x04".to_vec()
        );
    }

M examples/15.rs => examples/15.rs +2 -2
@@ 1,12 1,12 @@
use std::error::Error;

use cryptopals::aes::pkcs7_validation;
use cryptopals::aes::pkcs7_unpad;

fn main() -> Result<(), Box<dyn Error>> {
    println!(
        "{}",
        String::from_utf8(
            pkcs7_validation(b"ICE ICE BABY\x04\x04\x04\x04".to_vec(), 16)
            pkcs7_unpad(b"ICE ICE BABY\x04\x04\x04\x04".to_vec(), 16)
                .expect("valid padding detected as invalid")
        )
        .unwrap()

A examples/16.rs => examples/16.rs +82 -0
@@ 0,0 1,82 @@
use std::error::Error;

use cryptopals::aes::{cbc_decrypt, cbc_encrypt, random_iv, random_key};

const PREFIX: &str = "comment1=cooking%20MCs;userdata=";
const SUFFIX: &str = ";comment2=%20like%20a%20pound%20of%20bacon";

fn main() -> Result<(), Box<dyn Error>> {
    let key = random_key();
    let iv = random_iv();

    assert!(is_admin(
        attack(&|s| construct_and_encrypt(s, key.clone(), iv.clone())),
        key,
        iv
    ));

    Ok(())
}

fn attack(generator: &dyn Fn(String) -> Vec<u8>) -> Vec<u8> {
    let input = "among us".to_string();
    let ciphertext = generator(input.clone());

    let admin_fr = b";admin=true";

    let raw_length = PREFIX.len() + input.len() + SUFFIX.len();
    let padding = (16 - (raw_length % 16)) % 16;
    assert!(padding > admin_fr.len());

    // replace the padding with the payload
    // TODO: we could also just replace the suffix with the payload tbh
    let mut modded_ciphertext = ciphertext.clone();
    for (i, byte) in admin_fr.iter().enumerate() {
        let location = ciphertext.len() - 16 - admin_fr.len() - 1 + i;
        modded_ciphertext[location] ^= (padding as u8) ^ byte;
    }
    modded_ciphertext[ciphertext.len() - 1 - 16] ^= (padding as u8) ^ 1;

    modded_ciphertext
}

fn construct_and_encrypt(string: String, key: Vec<u8>, iv: Vec<u8>) -> Vec<u8> {
    let mut plaintext = String::new();
    plaintext.push_str(PREFIX);
    plaintext.push_str(&string.replace(';', "%3B").replace('=', "%3D"));
    plaintext.push_str(SUFFIX);

    // cbc_encrypt pads for us
    cbc_encrypt(plaintext.as_bytes().to_vec(), key, iv)
}

fn is_admin(ciphertext: Vec<u8>, key: Vec<u8>, iv: Vec<u8>) -> bool {
    let plaintext = cbc_decrypt(ciphertext, key, iv);
    let plaintext_string = String::from_utf8_lossy(&plaintext);

    let parsed = plaintext_string.split(';').map(|s| {
        let parts = s.split('=').collect::<Vec<_>>();
        assert!(parts.clone().len() == 2);

        (parts[0], parts[1])
    });

    parsed.into_iter().any(|(k, v)| k == "admin" && v == "true")
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_escaping() {
        let key = random_key();
        let iv = random_iv();

        assert!(!is_admin(
            construct_and_encrypt("k;admin=true".to_string(), key.clone(), iv.clone()),
            key,
            iv,
        ));
    }
}

M src/aes.rs => src/aes.rs +7 -7
@@ 33,7 33,7 @@ pub fn ecb_decrypt(ciphertext: Vec<u8>, key: Vec<u8>) -> Vec<u8> {
pub fn cbc_encrypt(plaintext: Vec<u8>, key: Vec<u8>, iv: Vec<u8>) -> Vec<u8> {
    let cipher = Cipher::aes_128_ecb();

    let padded_plaintext = pkcs7(plaintext, 16);
    let padded_plaintext = pkcs7_pad(plaintext, 16);

    let mut ciphertext = vec![];
    for (i, block) in padded_plaintext.chunks(16).enumerate() {


@@ 83,10 83,10 @@ pub fn cbc_decrypt(ciphertext: Vec<u8>, key: Vec<u8>, iv: Vec<u8>) -> Vec<u8> {
        plaintext.append(&mut xored_block);
    }

    plaintext
    pkcs7_unpad(plaintext, 16).unwrap()
}

pub fn pkcs7(bytes: Vec<u8>, block_size: usize) -> Vec<u8> {
pub fn pkcs7_pad(bytes: Vec<u8>, block_size: usize) -> Vec<u8> {
    let padding_size = block_size - (bytes.len() % block_size);
    let mut padded = bytes;
    for _ in 0..padding_size {


@@ 96,7 96,7 @@ pub fn pkcs7(bytes: Vec<u8>, block_size: usize) -> Vec<u8> {
    padded
}

pub fn pkcs7_validation<'a>(bytes: Vec<u8>, block_size: usize) -> Result<Vec<u8>, &'a str> {
pub fn pkcs7_unpad<'a>(bytes: Vec<u8>, block_size: usize) -> Result<Vec<u8>, &'a str> {
    if bytes.len() % block_size != 0 {
        return Err("invalid length");
    }


@@ 150,11 150,11 @@ mod tests {
    #[test]
    fn test_pkcs7_validation() {
        assert_eq!(
            pkcs7_validation(b"ICE ICE BABY\x04\x04\x04\x04".to_vec(), 16),
            pkcs7_unpad(b"ICE ICE BABY\x04\x04\x04\x04".to_vec(), 16),
            Ok(b"ICE ICE BABY".to_vec())
        );

        assert!(pkcs7_validation(b"ICE ICE BABY\x05\x05\x05\x05".to_vec(), 16).is_err());
        assert!(pkcs7_validation(b"ICE ICE BABY\x01\x02\x03\x04".to_vec(), 16).is_err());
        assert!(pkcs7_unpad(b"ICE ICE BABY\x05\x05\x05\x05".to_vec(), 16).is_err());
        assert!(pkcs7_unpad(b"ICE ICE BABY\x01\x02\x03\x04".to_vec(), 16).is_err());
    }
}