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