Skip to main content

encryption/guomi/
zuc.rs

1// Copyright (c) 2026 erik <erik@erik.xyz> — https://erik.xyz
2
3//! 国密 ZUC-128 流密码(GB/T 33133.1-2016)+ HMAC-SHA256(encrypt-then-MAC);
4//! 载荷:`v1 | IV(16) | MAC(32) | 密文(与密钥流异或)`。密钥 16 字节。
5
6use rand_core::{OsRng, RngCore};
7use zuc::zuc128::zuc128_xor_inplace;
8
9use crate::contract::{Identified, SymmetricCipher};
10use crate::error::Result;
11use crate::internal::etm;
12use crate::key::Key;
13
14const LABEL: &str = "ZUC";
15const IV_LEN: usize = 16;
16
17/// ZUC-128 加密器。
18#[derive(Debug)]
19pub struct Zuc128Encryptor {
20    key: Key<16>,
21}
22
23impl Zuc128Encryptor {
24    pub const IDENTIFIER: &'static str = "zuc-128";
25
26    pub fn new(key: [u8; 16]) -> Self {
27        Self { key: Key::new(key) }
28    }
29
30    pub fn from_slice(key: &[u8]) -> Result<Self> {
31        Ok(Self {
32            key: Key::from_slice(key, LABEL)?,
33        })
34    }
35}
36
37impl Identified for Zuc128Encryptor {
38    fn identifier(&self) -> &str {
39        Self::IDENTIFIER
40    }
41}
42
43impl SymmetricCipher for Zuc128Encryptor {
44    fn encrypt(&self, plaintext: &[u8]) -> Result<Vec<u8>> {
45        let mut iv = [0u8; IV_LEN];
46        OsRng.fill_bytes(&mut iv);
47
48        let mut buffer = plaintext.to_vec();
49        let bit_len = buffer.len() * 8;
50        zuc128_xor_inplace(self.key.as_bytes(), &iv, &mut buffer, bit_len);
51
52        Ok(etm::pack(&iv, &buffer, self.key.as_bytes()))
53    }
54
55    fn decrypt(&self, ciphertext: &[u8]) -> Result<Vec<u8>> {
56        let (iv, body) = etm::unpack_and_verify(ciphertext, IV_LEN, self.key.as_bytes(), LABEL)?;
57        // unpack_and_verify 已保证 IV 长度恰为 16。
58        let iv: &[u8; IV_LEN] = iv.try_into().expect("iv length was verified");
59
60        let mut buffer = body.to_vec();
61        let bit_len = buffer.len() * 8;
62        zuc128_xor_inplace(self.key.as_bytes(), iv, &mut buffer, bit_len);
63        Ok(buffer)
64    }
65}
66
67#[cfg(test)]
68mod tests {
69    use super::*;
70    use crate::error::Error;
71
72    /// GB/T 33133.1 标准向量:全零密钥 + 全零 IV 的前两个密钥流字为
73    /// `0x27bede74`、`0x018082da`(与 PHP 版 `GuomiZucTest` 固化的向量一致,
74    /// 也是 zuc crate 自带的 EXAMPLE1)。往返测试抓不出「密钥流整体错位」——
75    /// 这个向量是规格符合性的锚点。
76    #[test]
77    fn matches_gb_standard_keystream_vector() {
78        let mut buffer = [0u8; 8];
79        zuc128_xor_inplace(&[0u8; 16], &[0u8; 16], &mut buffer, 64);
80        assert_eq!(buffer, [0x27, 0xbe, 0xde, 0x74, 0x01, 0x80, 0x82, 0xda]);
81    }
82
83    #[test]
84    fn roundtrip_including_partial_word_tail() {
85        let encryptor = Zuc128Encryptor::new([3u8; 16]);
86        // 覆盖 4 字节外的尾巴(密钥流按字生成,长度不足时取前缀字节)。
87        for plaintext in [
88            b"".as_slice(),
89            b"a",
90            b"abc",
91            b"abcd",
92            b"abcde",
93            &[0xffu8; 40],
94        ] {
95            let blob = encryptor.encrypt(plaintext).unwrap();
96            assert_eq!(encryptor.decrypt(&blob).unwrap(), plaintext);
97        }
98    }
99
100    #[test]
101    fn ciphertext_differs_from_plaintext_and_tamper_fails() {
102        let encryptor = Zuc128Encryptor::new([3u8; 16]);
103        let blob = encryptor.encrypt(b"0123456789").unwrap();
104        assert_ne!(&blob[50..], b"0123456789");
105
106        let mut tampered = blob.clone();
107        let last = tampered.len() - 1;
108        tampered[last] ^= 0x01;
109        assert_eq!(
110            encryptor.decrypt(&tampered).unwrap_err(),
111            Error::MacVerificationFailed { label: LABEL }
112        );
113    }
114}