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
31pub struct Aes256 {
32    key: [u8; 32],
33}
34
35impl Aes256 {
36    /// Construct AES-256 with a compile-time-sized 256-bit key.
37    ///
38    /// The array reference deliberately makes an invalid key length
39    /// unrepresentable at this API boundary.
40    pub fn new(key: &[u8; 32]) -> Self {
41        Aes256 { key: *key }
42    }
43
44    /// Encrypt block-aligned plaintext in CBC mode without padding.
45    ///
46    /// Returns `CbcError::UnalignedLength` if `plaintext.len()` is not a
47    /// multiple of 16.
48    pub fn encrypt_cbc(&self, plaintext: &[u8], iv: &[u8; 16]) -> Result<Vec<u8>, CbcError> {
49        if !plaintext.len().is_multiple_of(BLOCK_SIZE) {
50            return Err(CbcError::UnalignedLength);
51        }
52        let mut buf = plaintext.to_vec();
53        let message_len = buf.len();
54        Aes256CbcEnc::new(&self.key.into(), iv.into())
55            .encrypt_padded::<NoPadding>(&mut buf, message_len)
56            .map_err(|_| CbcError::UnalignedLength)?;
57        Ok(buf)
58    }
59
60    /// Decrypt block-aligned ciphertext in CBC mode without removing padding.
61    ///
62    /// Returns `CbcError::UnalignedLength` if `ciphertext.len()` is not a
63    /// multiple of 16.
64    pub fn decrypt_cbc(&self, ciphertext: &[u8], iv: &[u8; 16]) -> Result<Vec<u8>, CbcError> {
65        if !ciphertext.len().is_multiple_of(BLOCK_SIZE) {
66            return Err(CbcError::UnalignedLength);
67        }
68        let mut buf = ciphertext.to_vec();
69        Aes256CbcDec::new(&self.key.into(), iv.into())
70            .decrypt_padded::<NoPadding>(&mut buf)
71            .map_err(|_| CbcError::UnalignedLength)?;
72        Ok(buf)
73    }
74}
75
76#[cfg(test)]
77mod tests {
78    use super::*;
79
80    #[test]
81    fn test_aes256_encrypt_decrypt_block() {
82        let key = [0u8; 32];
83        let iv = [0u8; 16];
84        let cipher = Aes256::new(&key);
85        let plaintext = [0u8; 16];
86        let encrypted = cipher.encrypt_cbc(&plaintext, &iv).unwrap();
87        let decrypted = cipher.decrypt_cbc(&encrypted, &iv).unwrap();
88        assert_eq!(decrypted, plaintext.to_vec());
89    }
90
91    #[test]
92    fn test_aes256_cbc_roundtrip() {
93        let key = [0x01u8; 32];
94        let iv = [0x02u8; 16];
95        let cipher = Aes256::new(&key);
96        let plaintext = [0x03u8; 32];
97        let encrypted = cipher.encrypt_cbc(&plaintext, &iv).unwrap();
98        let decrypted = cipher.decrypt_cbc(&encrypted, &iv).unwrap();
99        assert_eq!(decrypted, plaintext.to_vec());
100    }
101
102    #[test]
103    fn test_aes256_known_vector() {
104        // NIST AES-256 ECB test vector, verified via single-block CBC with zero IV
105        let key: [u8; 32] = [
106            0x60, 0x3d, 0xeb, 0x10, 0x15, 0xca, 0x71, 0xbe, 0x2b, 0x73, 0xae, 0xf0, 0x85, 0x7d,
107            0x77, 0x81, 0x1f, 0x35, 0x2c, 0x07, 0x3b, 0x61, 0x08, 0xd7, 0x2d, 0x98, 0x10, 0xa3,
108            0x09, 0x14, 0xdf, 0xf4,
109        ];
110        let plaintext: [u8; 16] = [
111            0x6b, 0xc1, 0xbe, 0xe2, 0x2e, 0x40, 0x9f, 0x96, 0xe9, 0x3d, 0x7e, 0x11, 0x73, 0x93,
112            0x17, 0x2a,
113        ];
114        let expected: [u8; 16] = [
115            0xf3, 0xee, 0xd1, 0xbd, 0xb5, 0xd2, 0xa0, 0x3c, 0x06, 0x4b, 0x5a, 0x7e, 0x3d, 0xb1,
116            0x81, 0xf8,
117        ];
118        let cipher = Aes256::new(&key);
119        let iv = [0u8; 16];
120        let result = cipher.encrypt_cbc(&plaintext, &iv).unwrap();
121        assert_eq!(&result[..16], &expected);
122    }
123
124    #[test]
125    fn test_aes256_nist_sp800_38a_cbc_four_block_vector() {
126        let key: [u8; 32] = decode_hex(concat!(
127            "603deb1015ca71be2b73aef0857d7781",
128            "1f352c073b6108d72d9810a30914dff4"
129        ))
130        .try_into()
131        .unwrap();
132        let iv: [u8; 16] = decode_hex("000102030405060708090a0b0c0d0e0f")
133            .try_into()
134            .unwrap();
135        let plaintext = decode_hex(concat!(
136            "6bc1bee22e409f96e93d7e117393172a",
137            "ae2d8a571e03ac9c9eb76fac45af8e51",
138            "30c81c46a35ce411e5fbc1191a0a52ef",
139            "f69f2445df4f9b17ad2b417be66c3710"
140        ));
141        let expected = decode_hex(concat!(
142            "f58c4c04d6e5f1ba779eabfb5f7bfbd6",
143            "9cfc4e967edb808d679f777bc6702c7d",
144            "39f23369a9d9bacfa530e26304231461",
145            "b2eb05e2c39be9fcda6c19078c6a9d1b"
146        ));
147        let cipher = Aes256::new(&key);
148
149        assert_eq!(cipher.encrypt_cbc(&plaintext, &iv).unwrap(), expected);
150        assert_eq!(cipher.decrypt_cbc(&expected, &iv).unwrap(), plaintext);
151    }
152
153    #[test]
154    fn test_aes256_empty_cbc_payload_is_stable() {
155        let cipher = Aes256::new(&[0x11; 32]);
156        assert!(cipher.encrypt_cbc(&[], &[0x22; 16]).unwrap().is_empty());
157        assert!(cipher.decrypt_cbc(&[], &[0x22; 16]).unwrap().is_empty());
158    }
159
160    #[test]
161    fn test_aes256_rejects_unaligned_ciphertext() {
162        assert_eq!(
163            Aes256::new(&[0; 32]).decrypt_cbc(&[0; 17], &[0; 16]),
164            Err(CbcError::UnalignedLength)
165        );
166    }
167
168    #[test]
169    fn test_aes256_rejects_unaligned_plaintext() {
170        assert_eq!(
171            Aes256::new(&[0; 32]).encrypt_cbc(&[0; 15], &[0; 16]),
172            Err(CbcError::UnalignedLength)
173        );
174    }
175
176    fn decode_hex(hex: &str) -> Vec<u8> {
177        (0..hex.len())
178            .step_by(2)
179            .map(|index| u8::from_str_radix(&hex[index..index + 2], 16).unwrap())
180            .collect()
181    }
182}