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::{Error, Result};
11use crate::internal::etm::{self, EtmKeys};
12
13const LABEL: &str = "ZUC";
14const IV_LEN: usize = 16;
15
16/// ZUC-128 加密器。
17#[derive(Debug)]
18pub struct Zuc128Encryptor {
19    keys: EtmKeys<16>,
20}
21
22impl Zuc128Encryptor {
23    pub const IDENTIFIER: &'static str = "zuc-128";
24
25    pub fn new(key: [u8; 16]) -> Self {
26        Self {
27            keys: EtmKeys::new(key),
28        }
29    }
30
31    pub fn from_slice(key: &[u8]) -> Result<Self> {
32        Ok(Self {
33            keys: EtmKeys::from_slice(key, LABEL)?,
34        })
35    }
36}
37
38impl Identified for Zuc128Encryptor {
39    fn identifier(&self) -> &str {
40        Self::IDENTIFIER
41    }
42}
43
44impl SymmetricCipher for Zuc128Encryptor {
45    fn encrypt(&self, plaintext: &[u8]) -> Result<Vec<u8>> {
46        let mut iv = [0u8; IV_LEN];
47        OsRng.fill_bytes(&mut iv);
48
49        let mut buffer = plaintext.to_vec();
50        let bit_len = bit_length(&buffer).ok_or(Error::EncryptionFailed { label: LABEL })?;
51        zuc128_xor_inplace(self.keys.cipher(), &iv, &mut buffer, bit_len);
52
53        Ok(etm::pack(&iv, &buffer, self.keys.mac()))
54    }
55
56    fn decrypt(&self, ciphertext: &[u8]) -> Result<Vec<u8>> {
57        let (iv, body) = etm::unpack_and_verify(ciphertext, IV_LEN, self.keys.mac(), LABEL)?;
58        // unpack_and_verify 已保证 IV 长度恰为 16。
59        let iv: &[u8; IV_LEN] = iv.try_into().expect("iv length was verified");
60
61        let mut buffer = body.to_vec();
62        let bit_len = bit_length(&buffer).ok_or(Error::DecryptionFailed { label: LABEL })?;
63        zuc128_xor_inplace(self.keys.cipher(), iv, &mut buffer, bit_len);
64        Ok(buffer)
65    }
66}
67
68/// 载荷的比特长度;`usize` 装不下时返回 `None`。
69///
70/// 64 位目标上不可能触发(需要 2^61 字节的载荷);32 位目标(wasm32 / i686 /
71/// armv7)上 512 MiB 即回绕,而底层 ZUC 实现拿到回绕后的 `bit_len` 会把超出
72/// 部分的密文静默清零——那是数据毁坏,必须在这里拦住而不是放过去。
73fn bit_length(data: &[u8]) -> Option<usize> {
74    data.len().checked_mul(8)
75}
76
77#[cfg(test)]
78mod tests {
79    use super::*;
80    use crate::error::Error;
81
82    /// GB/T 33133.1 标准向量:全零密钥 + 全零 IV 的前两个密钥流字为
83    /// `0x27bede74`、`0x018082da`(与 PHP 版 `GuomiZucTest` 固化的向量一致,
84    /// 也是 zuc crate 自带的 EXAMPLE1)。往返测试抓不出「密钥流整体错位」——
85    /// 这个向量是规格符合性的锚点。
86    #[test]
87    fn matches_gb_standard_keystream_vector() {
88        let mut buffer = [0u8; 8];
89        zuc128_xor_inplace(&[0u8; 16], &[0u8; 16], &mut buffer, 64);
90        assert_eq!(buffer, [0x27, 0xbe, 0xde, 0x74, 0x01, 0x80, 0x82, 0xda]);
91    }
92
93    #[test]
94    fn roundtrip_including_partial_word_tail() {
95        let encryptor = Zuc128Encryptor::new([3u8; 16]);
96        // 覆盖 4 字节外的尾巴(密钥流按字生成,长度不足时取前缀字节)。
97        for plaintext in [
98            b"".as_slice(),
99            b"a",
100            b"abc",
101            b"abcd",
102            b"abcde",
103            &[0xffu8; 40],
104        ] {
105            let blob = encryptor.encrypt(plaintext).unwrap();
106            assert_eq!(encryptor.decrypt(&blob).unwrap(), plaintext);
107        }
108    }
109
110    #[test]
111    fn ciphertext_differs_from_plaintext_and_tamper_fails() {
112        let encryptor = Zuc128Encryptor::new([3u8; 16]);
113        let blob = encryptor.encrypt(b"0123456789").unwrap();
114        assert_ne!(&blob[50..], b"0123456789");
115
116        let mut tampered = blob.clone();
117        let last = tampered.len() - 1;
118        tampered[last] ^= 0x01;
119        assert_eq!(
120            encryptor.decrypt(&tampered).unwrap_err(),
121            Error::MacVerificationFailed { label: LABEL }
122        );
123    }
124}