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