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#[derive(Debug, PartialEq)]
10pub enum CbcError {
11 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 pub fn new(key: &[u8; 32]) -> Self {
42 Aes256 { key: *key }
43 }
44
45 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 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 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 pub(crate) fn cbc_decryptor(&self, iv: &[u8; 16]) -> cbc::Decryptor<aes::Aes256> {
83 Aes256CbcDec::new(&self.key.into(), iv.into())
84 }
85
86 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 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 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}