Skip to main content

rns_crypto/
aes256.rs

1use alloc::vec::Vec;
2use core::fmt;
3
4use aes::cipher::{block_padding::NoPadding, BlockModeDecrypt, BlockModeEncrypt, KeyIvInit};
5
6const BLOCK_SIZE: usize = 16;
7
8/// Error returned by the raw (unpadded) AES-CBC helpers.
9#[derive(Debug, PartialEq)]
10pub enum CbcError {
11    /// The input length is not a multiple of the 16-byte block size.
12    UnalignedLength,
13}
14
15impl fmt::Display for CbcError {
16    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
17        match self {
18            CbcError::UnalignedLength => {
19                write!(f, "CBC input length is not a multiple of the block size")
20            }
21        }
22    }
23}
24
25#[cfg(feature = "std")]
26impl std::error::Error for CbcError {}
27
28type Aes256CbcEnc = cbc::Encryptor<aes::Aes256>;
29type Aes256CbcDec = cbc::Decryptor<aes::Aes256>;
30
31#[derive(Clone)]
32pub struct Aes256 {
33    key: [u8; 32],
34}
35
36impl Aes256 {
37    /// Construct AES-256 with a compile-time-sized 256-bit key.
38    ///
39    /// The array reference deliberately makes an invalid key length
40    /// unrepresentable at this API boundary.
41    pub fn new(key: &[u8; 32]) -> Self {
42        Aes256 { key: *key }
43    }
44
45    /// Encrypt block-aligned plaintext in CBC mode without padding.
46    ///
47    /// Returns `CbcError::UnalignedLength` if `plaintext.len()` is not a
48    /// multiple of 16.
49    pub fn encrypt_cbc(&self, plaintext: &[u8], iv: &[u8; 16]) -> Result<Vec<u8>, CbcError> {
50        if !plaintext.len().is_multiple_of(BLOCK_SIZE) {
51            return Err(CbcError::UnalignedLength);
52        }
53        let mut buf = plaintext.to_vec();
54        self.encrypt_cbc_in_place(&mut buf, iv)?;
55        Ok(buf)
56    }
57
58    /// [`encrypt_cbc`](Self::encrypt_cbc) over `buf` in place.
59    pub fn encrypt_cbc_in_place(&self, buf: &mut [u8], iv: &[u8; 16]) -> Result<(), CbcError> {
60        if !buf.len().is_multiple_of(BLOCK_SIZE) {
61            return Err(CbcError::UnalignedLength);
62        }
63        #[cfg(target_arch = "x86_64")]
64        if crate::aes256_ni::encrypt_cbc(&self.key, iv, buf) {
65            return Ok(());
66        }
67        self.encrypt_cbc_portable(buf, iv)
68    }
69
70    /// [`encrypt_cbc_in_place`](Self::encrypt_cbc_in_place) through the
71    /// generic cipher, on CPUs without a dedicated path.
72    fn encrypt_cbc_portable(&self, buf: &mut [u8], iv: &[u8; 16]) -> Result<(), CbcError> {
73        let message_len = buf.len();
74        Aes256CbcEnc::new(&self.key.into(), iv.into())
75            .encrypt_padded::<NoPadding>(buf, message_len)
76            .map_err(|_| CbcError::UnalignedLength)?;
77        Ok(())
78    }
79
80    /// A CBC decryptor that keeps its chaining state across calls, for
81    /// ciphertext that arrives in pieces.
82    pub(crate) fn cbc_decryptor(&self, iv: &[u8; 16]) -> cbc::Decryptor<aes::Aes256> {
83        Aes256CbcDec::new(&self.key.into(), iv.into())
84    }
85
86    /// Decrypt block-aligned ciphertext in CBC mode without removing padding.
87    ///
88    /// Returns `CbcError::UnalignedLength` if `ciphertext.len()` is not a
89    /// multiple of 16.
90    pub fn decrypt_cbc(&self, ciphertext: &[u8], iv: &[u8; 16]) -> Result<Vec<u8>, CbcError> {
91        if !ciphertext.len().is_multiple_of(BLOCK_SIZE) {
92            return Err(CbcError::UnalignedLength);
93        }
94        let mut buf = ciphertext.to_vec();
95        self.decrypt_cbc_in_place(&mut buf, iv)?;
96        Ok(buf)
97    }
98
99    /// [`Self::decrypt_cbc`] in place: `buf` holds the ciphertext and is
100    /// left holding the plaintext, padding included.
101    pub fn decrypt_cbc_in_place(&self, buf: &mut [u8], iv: &[u8; 16]) -> Result<(), CbcError> {
102        if !buf.len().is_multiple_of(BLOCK_SIZE) {
103            return Err(CbcError::UnalignedLength);
104        }
105        Aes256CbcDec::new(&self.key.into(), iv.into())
106            .decrypt_padded::<NoPadding>(buf)
107            .map_err(|_| CbcError::UnalignedLength)?;
108        Ok(())
109    }
110}
111
112#[cfg(test)]
113mod tests {
114    use super::*;
115
116    #[test]
117    fn dedicated_cbc_encryption_matches_the_generic_cipher() {
118        let mut seed = 0x9e37_79b9u32;
119        let mut next = move || {
120            seed = seed.wrapping_mul(1_664_525).wrapping_add(1_013_904_223);
121            (seed >> 24) as u8
122        };
123        for blocks in [1usize, 2, 3, 16, 255, 4096] {
124            for _ in 0..8 {
125                let key: [u8; 32] = core::array::from_fn(|_| next());
126                let iv: [u8; 16] = core::array::from_fn(|_| next());
127                let plain: Vec<u8> = (0..blocks * 16).map(|_| next()).collect();
128                let aes = Aes256::new(&key);
129                let mut fast = plain.clone();
130                aes.encrypt_cbc_in_place(&mut fast, &iv).unwrap();
131                let mut generic = plain.clone();
132                aes.encrypt_cbc_portable(&mut generic, &iv).unwrap();
133                assert_eq!(fast, generic);
134                assert_eq!(aes.decrypt_cbc(&fast, &iv).unwrap(), plain);
135            }
136        }
137    }
138
139    #[test]
140    fn test_aes256_encrypt_decrypt_block() {
141        let key = [0u8; 32];
142        let iv = [0u8; 16];
143        let cipher = Aes256::new(&key);
144        let plaintext = [0u8; 16];
145        let encrypted = cipher.encrypt_cbc(&plaintext, &iv).unwrap();
146        let decrypted = cipher.decrypt_cbc(&encrypted, &iv).unwrap();
147        assert_eq!(decrypted, plaintext.to_vec());
148    }
149
150    #[test]
151    fn test_aes256_cbc_roundtrip() {
152        let key = [0x01u8; 32];
153        let iv = [0x02u8; 16];
154        let cipher = Aes256::new(&key);
155        let plaintext = [0x03u8; 32];
156        let encrypted = cipher.encrypt_cbc(&plaintext, &iv).unwrap();
157        let decrypted = cipher.decrypt_cbc(&encrypted, &iv).unwrap();
158        assert_eq!(decrypted, plaintext.to_vec());
159    }
160
161    #[test]
162    fn test_aes256_known_vector() {
163        // NIST AES-256 ECB test vector, verified via single-block CBC with zero IV
164        let key: [u8; 32] = [
165            0x60, 0x3d, 0xeb, 0x10, 0x15, 0xca, 0x71, 0xbe, 0x2b, 0x73, 0xae, 0xf0, 0x85, 0x7d,
166            0x77, 0x81, 0x1f, 0x35, 0x2c, 0x07, 0x3b, 0x61, 0x08, 0xd7, 0x2d, 0x98, 0x10, 0xa3,
167            0x09, 0x14, 0xdf, 0xf4,
168        ];
169        let plaintext: [u8; 16] = [
170            0x6b, 0xc1, 0xbe, 0xe2, 0x2e, 0x40, 0x9f, 0x96, 0xe9, 0x3d, 0x7e, 0x11, 0x73, 0x93,
171            0x17, 0x2a,
172        ];
173        let expected: [u8; 16] = [
174            0xf3, 0xee, 0xd1, 0xbd, 0xb5, 0xd2, 0xa0, 0x3c, 0x06, 0x4b, 0x5a, 0x7e, 0x3d, 0xb1,
175            0x81, 0xf8,
176        ];
177        let cipher = Aes256::new(&key);
178        let iv = [0u8; 16];
179        let result = cipher.encrypt_cbc(&plaintext, &iv).unwrap();
180        assert_eq!(&result[..16], &expected);
181    }
182
183    #[test]
184    fn test_aes256_nist_sp800_38a_cbc_four_block_vector() {
185        let key: [u8; 32] = decode_hex(concat!(
186            "603deb1015ca71be2b73aef0857d7781",
187            "1f352c073b6108d72d9810a30914dff4"
188        ))
189        .try_into()
190        .unwrap();
191        let iv: [u8; 16] = decode_hex("000102030405060708090a0b0c0d0e0f")
192            .try_into()
193            .unwrap();
194        let plaintext = decode_hex(concat!(
195            "6bc1bee22e409f96e93d7e117393172a",
196            "ae2d8a571e03ac9c9eb76fac45af8e51",
197            "30c81c46a35ce411e5fbc1191a0a52ef",
198            "f69f2445df4f9b17ad2b417be66c3710"
199        ));
200        let expected = decode_hex(concat!(
201            "f58c4c04d6e5f1ba779eabfb5f7bfbd6",
202            "9cfc4e967edb808d679f777bc6702c7d",
203            "39f23369a9d9bacfa530e26304231461",
204            "b2eb05e2c39be9fcda6c19078c6a9d1b"
205        ));
206        let cipher = Aes256::new(&key);
207
208        assert_eq!(cipher.encrypt_cbc(&plaintext, &iv).unwrap(), expected);
209        assert_eq!(cipher.decrypt_cbc(&expected, &iv).unwrap(), plaintext);
210    }
211
212    #[test]
213    fn test_aes256_empty_cbc_payload_is_stable() {
214        let cipher = Aes256::new(&[0x11; 32]);
215        assert!(cipher.encrypt_cbc(&[], &[0x22; 16]).unwrap().is_empty());
216        assert!(cipher.decrypt_cbc(&[], &[0x22; 16]).unwrap().is_empty());
217    }
218
219    #[test]
220    fn test_aes256_rejects_unaligned_ciphertext() {
221        assert_eq!(
222            Aes256::new(&[0; 32]).decrypt_cbc(&[0; 17], &[0; 16]),
223            Err(CbcError::UnalignedLength)
224        );
225    }
226
227    #[test]
228    fn test_aes256_rejects_unaligned_plaintext() {
229        assert_eq!(
230            Aes256::new(&[0; 32]).encrypt_cbc(&[0; 15], &[0; 16]),
231            Err(CbcError::UnalignedLength)
232        );
233    }
234
235    fn decode_hex(hex: &str) -> Vec<u8> {
236        (0..hex.len())
237            .step_by(2)
238            .map(|index| u8::from_str_radix(&hex[index..index + 2], 16).unwrap())
239            .collect()
240    }
241}