Skip to main content

encryption/encryptor/
kuznyechikcbc.rs

1// Copyright (c) 2026 erik <erik@erik.xyz> — https://erik.xyz
2
3//! Kuznyechik-256-CBC + HMAC-SHA256(encrypt-then-MAC,PKCS#7 填充);
4//! 载荷:`v1 | IV(16) | MAC(32) | 密文`。密钥固定 256 位。
5
6use cbc::cipher::{BlockDecryptMut, BlockEncryptMut, KeyIvInit, block_padding::Pkcs7};
7use cbc::{Decryptor, Encryptor};
8use kuznyechik::Kuznyechik;
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 = "Kuznyechik-256-CBC";
17const IV_LEN: usize = 16;
18
19/// Kuznyechik-256-CBC 加密器(CBC + HMAC;GOST R 34.12-2015 块密码,密钥长度
20/// 固定为 256 位,没有 128 / 192 位变体)。
21#[derive(Debug)]
22pub struct Kuznyechik256CbcEncryptor {
23    key: Key<32>,
24}
25
26impl Kuznyechik256CbcEncryptor {
27    pub const IDENTIFIER: &'static str = "kuznyechik-256-cbc-hmac";
28
29    pub fn new(key: [u8; 32]) -> Self {
30        Self { key: Key::new(key) }
31    }
32
33    pub fn from_slice(key: &[u8]) -> Result<Self> {
34        Ok(Self {
35            key: Key::from_slice(key, LABEL)?,
36        })
37    }
38}
39
40impl Identified for Kuznyechik256CbcEncryptor {
41    fn identifier(&self) -> &str {
42        Self::IDENTIFIER
43    }
44}
45
46impl SymmetricCipher for Kuznyechik256CbcEncryptor {
47    fn encrypt(&self, plaintext: &[u8]) -> Result<Vec<u8>> {
48        let mut iv = [0u8; IV_LEN];
49        OsRng.fill_bytes(&mut iv);
50
51        let ciphertext = Encryptor::<Kuznyechik>::new_from_slices(self.key.as_bytes(), &iv)
52            .expect("key and iv lengths are fixed")
53            .encrypt_padded_vec_mut::<Pkcs7>(plaintext);
54
55        Ok(etm::pack(&iv, &ciphertext, self.key.as_bytes()))
56    }
57
58    fn decrypt(&self, ciphertext: &[u8]) -> Result<Vec<u8>> {
59        let (iv, body) = etm::unpack_and_verify(ciphertext, IV_LEN, self.key.as_bytes(), LABEL)?;
60
61        Decryptor::<Kuznyechik>::new_from_slices(self.key.as_bytes(), iv)
62            .expect("key and iv lengths were verified")
63            .decrypt_padded_vec_mut::<Pkcs7>(body)
64            .map_err(|_| Error::DecryptionFailed { label: LABEL })
65    }
66}
67
68#[cfg(test)]
69mod tests {
70    use super::*;
71
72    /// 十六进制字符串 → 字节:KAT 向量照抄标准文本,避免手工转写。
73    fn unhex(s: &str) -> Vec<u8> {
74        (0..s.len() / 2)
75            .map(|i| u8::from_str_radix(&s[2 * i..2 * i + 2], 16).unwrap())
76            .collect()
77    }
78
79    /// RFC 7801 Appendix A.1 的 Kuznyechik 示例(GOST R 34.12-2015 §5.5 同源):
80    /// 直接调用裸块密码 —— 封装层的往返测试抓不出「轮函数 / 密钥表整体错位」。
81    #[test]
82    fn bare_block_cipher_matches_rfc7801_vector() {
83        use cbc::cipher::{Block, BlockEncrypt, KeyInit};
84
85        let key = unhex("8899aabbccddeeff0011223344556677fedcba98765432100123456789abcdef");
86        let plaintext = unhex("1122334455667700ffeeddccbbaa9988");
87        let expected = unhex("7f679d90bebc24305a468d42b9d4edcd");
88
89        let cipher = Kuznyechik::new_from_slice(&key).unwrap();
90        let mut block = Block::<Kuznyechik>::clone_from_slice(&plaintext);
91        cipher.encrypt_block(&mut block);
92        assert_eq!(block.as_slice(), expected.as_slice());
93    }
94
95    #[test]
96    fn roundtrip_at_padding_boundaries() {
97        let encryptor = Kuznyechik256CbcEncryptor::new([5u8; 32]);
98        for plaintext in [
99            b"".as_slice(),
100            b"x",
101            b"0123456789abcdef",
102            b"0123456789abcdefg",
103        ] {
104            let blob = encryptor.encrypt(plaintext).unwrap();
105            assert_eq!(encryptor.decrypt(&blob).unwrap(), plaintext);
106        }
107    }
108
109    #[test]
110    fn tampered_blob_fails_mac_before_decrypt() {
111        let encryptor = Kuznyechik256CbcEncryptor::new([5u8; 32]);
112        let mut blob = encryptor.encrypt(b"secret").unwrap();
113        let last = blob.len() - 1;
114        blob[last] ^= 0x01;
115        assert_eq!(
116            encryptor.decrypt(&blob).unwrap_err(),
117            Error::MacVerificationFailed { label: LABEL }
118        );
119    }
120
121    #[test]
122    fn wrong_key_fails_mac() {
123        let blob = Kuznyechik256CbcEncryptor::new([5u8; 32])
124            .encrypt(b"secret")
125            .unwrap();
126        let other = Kuznyechik256CbcEncryptor::new([6u8; 32]);
127        assert_eq!(
128            other.decrypt(&blob).unwrap_err(),
129            Error::MacVerificationFailed { label: LABEL }
130        );
131    }
132
133    #[test]
134    fn iv_is_fresh_per_encryption() {
135        let encryptor = Kuznyechik256CbcEncryptor::new([5u8; 32]);
136        let first = encryptor.encrypt(b"same plaintext").unwrap();
137        let second = encryptor.encrypt(b"same plaintext").unwrap();
138        assert_ne!(first, second);
139    }
140
141    #[test]
142    fn invalid_prefix_and_too_short_are_rejected() {
143        let encryptor = Kuznyechik256CbcEncryptor::new([5u8; 32]);
144        assert_eq!(
145            encryptor.decrypt(b"v9 not a blob").unwrap_err(),
146            Error::InvalidPrefix { label: LABEL }
147        );
148        assert_eq!(
149            encryptor.decrypt(b"v1 short").unwrap_err(),
150            Error::TooShort { label: LABEL }
151        );
152    }
153
154    #[test]
155    fn key_length_is_checked() {
156        assert_eq!(
157            Kuznyechik256CbcEncryptor::from_slice(&[0u8; 16]).unwrap_err(),
158            Error::InvalidKeyLength {
159                label: LABEL,
160                expected: 32,
161                got: 16
162            }
163        );
164    }
165}