Skip to main content

encryption/encryptor/
camellia256cbc.rs

1// Copyright (c) 2026 erik <erik@erik.xyz> — https://erik.xyz
2
3//! Camellia-256-CBC + HMAC-SHA256(encrypt-then-MAC,PKCS#7 填充);
4//! 载荷:`v1 | IV(16) | MAC(32) | 密文`。
5
6use camellia::Camellia256;
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 = "Camellia-256-CBC";
17const IV_LEN: usize = 16;
18
19/// Camellia-256-CBC 加密器(CBC + HMAC;ISO/IEC 18033-3 块密码)。
20#[derive(Debug)]
21pub struct Camellia256CbcEncryptor {
22    key: Key<32>,
23}
24
25impl Camellia256CbcEncryptor {
26    pub const IDENTIFIER: &'static str = "camellia-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 Camellia256CbcEncryptor {
40    fn identifier(&self) -> &str {
41        Self::IDENTIFIER
42    }
43}
44
45impl SymmetricCipher for Camellia256CbcEncryptor {
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::<Camellia256>::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::<Camellia256>::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    /// 十六进制字符串 → 字节:KAT 向量照抄标准文本,避免手工转写。
72    fn unhex(s: &str) -> Vec<u8> {
73        (0..s.len() / 2)
74            .map(|i| u8::from_str_radix(&s[2 * i..2 * i + 2], 16).unwrap())
75            .collect()
76    }
77
78    /// RFC 3713 Appendix A 的 Camellia-256 示例(ISO/IEC 18033-3 同源):
79    /// 直接调用裸块密码 —— 封装层的往返测试抓不出「轮函数 / 密钥表整体错位」。
80    #[test]
81    fn bare_block_cipher_matches_rfc3713_vector() {
82        use cbc::cipher::{Block, BlockEncrypt, KeyInit};
83
84        let key = unhex("0123456789abcdeffedcba987654321000112233445566778899aabbccddeeff");
85        let plaintext = unhex("0123456789abcdeffedcba9876543210");
86        let expected = unhex("9acc237dff16d76c20ef7c919e3a7509");
87
88        let cipher = Camellia256::new_from_slice(&key).unwrap();
89        let mut block = Block::<Camellia256>::clone_from_slice(&plaintext);
90        cipher.encrypt_block(&mut block);
91        assert_eq!(block.as_slice(), expected.as_slice());
92    }
93
94    #[test]
95    fn roundtrip_at_padding_boundaries() {
96        let encryptor = Camellia256CbcEncryptor::new([5u8; 32]);
97        for plaintext in [
98            b"".as_slice(),
99            b"x",
100            b"0123456789abcdef",
101            b"0123456789abcdefg",
102        ] {
103            let blob = encryptor.encrypt(plaintext).unwrap();
104            assert_eq!(encryptor.decrypt(&blob).unwrap(), plaintext);
105        }
106    }
107
108    #[test]
109    fn tampered_blob_fails_mac_before_decrypt() {
110        let encryptor = Camellia256CbcEncryptor::new([5u8; 32]);
111        let mut blob = encryptor.encrypt(b"secret").unwrap();
112        let last = blob.len() - 1;
113        blob[last] ^= 0x01;
114        assert_eq!(
115            encryptor.decrypt(&blob).unwrap_err(),
116            Error::MacVerificationFailed { label: LABEL }
117        );
118    }
119
120    #[test]
121    fn wrong_key_fails_mac() {
122        let blob = Camellia256CbcEncryptor::new([5u8; 32])
123            .encrypt(b"secret")
124            .unwrap();
125        let other = Camellia256CbcEncryptor::new([6u8; 32]);
126        assert_eq!(
127            other.decrypt(&blob).unwrap_err(),
128            Error::MacVerificationFailed { label: LABEL }
129        );
130    }
131
132    #[test]
133    fn iv_is_fresh_per_encryption() {
134        let encryptor = Camellia256CbcEncryptor::new([5u8; 32]);
135        let first = encryptor.encrypt(b"same plaintext").unwrap();
136        let second = encryptor.encrypt(b"same plaintext").unwrap();
137        assert_ne!(first, second);
138    }
139
140    #[test]
141    fn invalid_prefix_and_too_short_are_rejected() {
142        let encryptor = Camellia256CbcEncryptor::new([5u8; 32]);
143        assert_eq!(
144            encryptor.decrypt(b"v9 not a blob").unwrap_err(),
145            Error::InvalidPrefix { label: LABEL }
146        );
147        assert_eq!(
148            encryptor.decrypt(b"v1 short").unwrap_err(),
149            Error::TooShort { label: LABEL }
150        );
151    }
152
153    #[test]
154    fn key_length_is_checked() {
155        assert_eq!(
156            Camellia256CbcEncryptor::from_slice(&[0u8; 16]).unwrap_err(),
157            Error::InvalidKeyLength {
158                label: LABEL,
159                expected: 32,
160                got: 16
161            }
162        );
163    }
164}