1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126
//! A simple Rust crate to exploit CBC-PKCS7 padding oracles.
//! See [decrypt] or the examples on how to use.
#![no_std]
#![feature(error_in_core)]
extern crate alloc;
use alloc::vec::Vec;
use thiserror::Error;
#[derive(Error, Debug)]
pub enum Error {
#[error("invalid ciphertext size. The length should be a multiple of {blocksize}, but the length is {found}")]
WrongSize { blocksize: usize, found: usize },
#[error(
"couldn't decrypt the data. Make sure your oracle is valid and that PKCS7 padding is used"
)]
InvalidPadding,
}
type Result<T> = core::result::Result<T, Error>;
/// Decrypt a ciphertext using an oracle function.
/// Note that this assumes the IV is prepended to the ciphertext.
/// If that's not the case, the first block won't be decrypted.
///
/// # Example
/// ```
/// use aes::cipher::{
/// block_padding::{Pkcs7, RawPadding},
/// BlockDecryptMut, BlockEncryptMut, KeyIvInit,
/// };
///
/// type Aes128CbcEnc = cbc::Encryptor<aes::Aes128>;
/// type Aes128CbcDec = cbc::Decryptor<aes::Aes128>;
///
/// const KEY: [u8; 16] = [0u8; 16];
/// const IV: [u8; 16] = [0u8; 16];
///
/// fn oracle(ciphertext: &[u8]) -> bool {
/// let mut buf = ciphertext.to_vec();
///
/// Aes128CbcDec::new(&KEY.into(), &IV.into())
/// .decrypt_padded_mut::<Pkcs7>(&mut buf)
/// .is_ok()
/// }
///
/// # let plaintext = b"000000Now that the party is jumping";
///
/// # let mut ciphertext = vec![0u8; (plaintext.len() / 16 + 1) * 16];
///
/// # ciphertext[..plaintext.len()].copy_from_slice(plaintext);
/// # let ciphertext = Aes128CbcEnc::new(&KEY.into(), &IV.into())
/// # .encrypt_padded_mut::<Pkcs7>(&mut ciphertext, plaintext.len())
/// # .unwrap();
///
/// # let mut iv = IV.to_vec();
///
/// # iv.extend_from_slice(ciphertext);
/// # let ciphertext = iv;
///
/// // Perform the attack
/// let plaintext = padding_oracle::decrypt(&ciphertext, 16, oracle).unwrap();
///```
pub fn decrypt(ciphertext: &[u8], blocksize: usize, oracle: fn(&[u8]) -> bool) -> Result<Vec<u8>> {
// Returns if ciphertext length does not align with blocks
if ciphertext.len() % blocksize != 0 {
return Err(Error::WrongSize {
blocksize,
found: ciphertext.len(),
});
}
let mut plaintext = b"".to_vec();
let mut ciphertext = ciphertext.to_vec();
for _ in 0..ciphertext.len() / blocksize - 1 {
// Loop to bruteforce one block
for i in 1..=blocksize {
let offset = ciphertext.len() - blocksize - i;
let initial_byte = ciphertext[offset];
let mut ciphertext = ciphertext.to_vec();
// Fix remaining bytes of the padding
for j in 1..i {
ciphertext[offset + j] = i as u8 ^ plaintext[j - 1] ^ ciphertext[offset + j];
}
let mut found = false;
for k in 0..=255u8 {
ciphertext[offset] = k;
if oracle(&ciphertext) {
// Make sure this is the padding we're looking for
// See https://crypto.stackexchange.com/questions/40800/is-the-padding-oracle-attack-deterministic
if offset % blocksize == 0 || {
let mut ciphertext = ciphertext.clone();
ciphertext[offset - 1] = !ciphertext[offset - 1];
oracle(&ciphertext)
} {
plaintext.insert(0, initial_byte ^ k ^ i as u8);
found = true;
break;
};
}
}
if !found {
// If no byte worked, something went wrong
return Err(Error::InvalidPadding);
}
}
// Cut the last block
ciphertext.truncate(ciphertext.len() - blocksize);
}
Ok(plaintext)
}