Skip to main content

encryption/encryptor/
aes256cbc.rs

1// Copyright (c) 2026 erik <erik@erik.xyz> — https://erik.xyz
2
3//! AES-256-CBC + HMAC-SHA256(encrypt-then-MAC,PKCS#7 填充);
4//! 载荷:`v1 | IV(16) | MAC(32) | 密文`。
5
6use aes::Aes256;
7use cbc::cipher::{BlockDecryptMut, BlockEncryptMut, KeyIvInit, block_padding::Pkcs7};
8use cbc::{Decryptor, Encryptor};
9use rand_core::{OsRng, RngCore};
10
11use crate::contract::{Identified, SymmetricCipher};
12use crate::error::{Error, Result};
13use crate::internal::etm;
14use crate::key::Key;
15
16const LABEL: &str = "AES-256-CBC";
17const IV_LEN: usize = 16;
18
19/// AES-256-CBC 加密器(CBC + HMAC,兼容旧环境)。
20#[derive(Debug)]
21pub struct Aes256CbcEncryptor {
22    key: Key<32>,
23}
24
25impl Aes256CbcEncryptor {
26    pub const IDENTIFIER: &'static str = "aes-256-cbc-hmac";
27
28    pub fn new(key: [u8; 32]) -> Self {
29        Self { key: Key::new(key) }
30    }
31
32    pub fn from_slice(key: &[u8]) -> Result<Self> {
33        Ok(Self {
34            key: Key::from_slice(key, LABEL)?,
35        })
36    }
37}
38
39impl Identified for Aes256CbcEncryptor {
40    fn identifier(&self) -> &str {
41        Self::IDENTIFIER
42    }
43}
44
45impl SymmetricCipher for Aes256CbcEncryptor {
46    fn encrypt(&self, plaintext: &[u8]) -> Result<Vec<u8>> {
47        let mut iv = [0u8; IV_LEN];
48        OsRng.fill_bytes(&mut iv);
49
50        let ciphertext = Encryptor::<Aes256>::new_from_slices(self.key.as_bytes(), &iv)
51            .expect("key and iv lengths are fixed")
52            .encrypt_padded_vec_mut::<Pkcs7>(plaintext);
53
54        Ok(etm::pack(&iv, &ciphertext, self.key.as_bytes()))
55    }
56
57    fn decrypt(&self, ciphertext: &[u8]) -> Result<Vec<u8>> {
58        let (iv, body) = etm::unpack_and_verify(ciphertext, IV_LEN, self.key.as_bytes(), LABEL)?;
59
60        Decryptor::<Aes256>::new_from_slices(self.key.as_bytes(), iv)
61            .expect("key and iv lengths were verified")
62            .decrypt_padded_vec_mut::<Pkcs7>(body)
63            .map_err(|_| Error::DecryptionFailed { label: LABEL })
64    }
65}
66
67#[cfg(test)]
68mod tests {
69    use super::*;
70
71    #[test]
72    fn roundtrip_even_for_empty_plaintext() {
73        let encryptor = Aes256CbcEncryptor::new([5u8; 32]);
74        for plaintext in [
75            b"".as_slice(),
76            b"x",
77            b"0123456789abcdef",
78            b"0123456789abcdefg",
79        ] {
80            let blob = encryptor.encrypt(plaintext).unwrap();
81            assert_eq!(encryptor.decrypt(&blob).unwrap(), plaintext);
82        }
83    }
84
85    #[test]
86    fn tampered_blob_fails_mac_before_decrypt() {
87        let encryptor = Aes256CbcEncryptor::new([5u8; 32]);
88        let mut blob = encryptor.encrypt(b"secret").unwrap();
89        let last = blob.len() - 1;
90        blob[last] ^= 0x01;
91        assert_eq!(
92            encryptor.decrypt(&blob).unwrap_err(),
93            Error::MacVerificationFailed { label: LABEL }
94        );
95    }
96
97    #[test]
98    fn wrong_key_fails_mac() {
99        let blob = Aes256CbcEncryptor::new([5u8; 32])
100            .encrypt(b"secret")
101            .unwrap();
102        let other = Aes256CbcEncryptor::new([6u8; 32]);
103        assert_eq!(
104            other.decrypt(&blob).unwrap_err(),
105            Error::MacVerificationFailed { label: LABEL }
106        );
107    }
108
109    #[test]
110    fn key_length_is_checked() {
111        assert_eq!(
112            Aes256CbcEncryptor::from_slice(&[0u8; 33]).unwrap_err(),
113            Error::InvalidKeyLength {
114                label: LABEL,
115                expected: 32,
116                got: 33
117            }
118        );
119    }
120}