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}