padding_oracle/
lib.rs

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