encryption/encryptor/
kuznyechikcbc.rs1use 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#[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 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 #[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}