Skip to main content

encryption/guomi/
sm4.rs

1// Copyright (c) 2026 erik <erik@erik.xyz> — https://erik.xyz
2
3//! 国密 SM4-CBC(PKCS#7 填充)+ HMAC-SHA256(encrypt-then-MAC);
4//! 载荷:`v1 | IV(16) | MAC(32) | 密文`。密钥 16 字节。
5
6use cbc::cipher::{BlockDecryptMut, BlockEncryptMut, KeyIvInit, block_padding::Pkcs7};
7use cbc::{Decryptor, Encryptor};
8use rand_core::{OsRng, RngCore};
9use sm4::Sm4;
10
11use crate::contract::{Identified, SymmetricCipher};
12use crate::error::{Error, Result};
13use crate::internal::etm;
14use crate::key::Key;
15
16const LABEL: &str = "SM4";
17const IV_LEN: usize = 16;
18
19/// SM4-CBC 加密器。
20#[derive(Debug)]
21pub struct Sm4CbcEncryptor {
22    key: Key<16>,
23}
24
25impl Sm4CbcEncryptor {
26    pub const IDENTIFIER: &'static str = "sm4-cbc";
27
28    pub fn new(key: [u8; 16]) -> 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 Sm4CbcEncryptor {
40    fn identifier(&self) -> &str {
41        Self::IDENTIFIER
42    }
43}
44
45impl SymmetricCipher for Sm4CbcEncryptor {
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::<Sm4>::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::<Sm4>::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_and_tamper_detection() {
73        let encryptor = Sm4CbcEncryptor::new([4u8; 16]);
74        for plaintext in [b"".as_slice(), b"plain", b"0123456789abcdef"] {
75            let blob = encryptor.encrypt(plaintext).unwrap();
76            assert_eq!(encryptor.decrypt(&blob).unwrap(), plaintext);
77        }
78
79        let mut blob = encryptor.encrypt(b"data").unwrap();
80        let last = blob.len() - 1;
81        blob[last] ^= 0x01;
82        assert_eq!(
83            encryptor.decrypt(&blob).unwrap_err(),
84            Error::MacVerificationFailed { label: LABEL }
85        );
86    }
87
88    #[test]
89    fn key_length_is_checked() {
90        assert_eq!(
91            Sm4CbcEncryptor::from_slice(&[0u8; 32]).unwrap_err(),
92            Error::InvalidKeyLength {
93                label: LABEL,
94                expected: 16,
95                got: 32
96            }
97        );
98    }
99}