1use 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#[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 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 #[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 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}