use rand_core::{OsRng, RngCore};
use zuc::zuc128::zuc128_xor_inplace;
use crate::contract::{Identified, SymmetricCipher};
use crate::error::{Error, Result};
use crate::internal::etm::{self, EtmKeys};
const LABEL: &str = "ZUC";
const IV_LEN: usize = 16;
#[derive(Debug)]
pub struct Zuc128Encryptor {
keys: EtmKeys<16>,
}
impl Zuc128Encryptor {
pub const IDENTIFIER: &'static str = "zuc-128";
pub fn new(key: [u8; 16]) -> Self {
Self {
keys: EtmKeys::new(key),
}
}
pub fn from_slice(key: &[u8]) -> Result<Self> {
Ok(Self {
keys: EtmKeys::from_slice(key, LABEL)?,
})
}
}
impl Identified for Zuc128Encryptor {
fn identifier(&self) -> &str {
Self::IDENTIFIER
}
}
impl SymmetricCipher for Zuc128Encryptor {
fn encrypt(&self, plaintext: &[u8]) -> Result<Vec<u8>> {
let mut iv = [0u8; IV_LEN];
OsRng.fill_bytes(&mut iv);
let mut buffer = plaintext.to_vec();
let bit_len = bit_length(&buffer).ok_or(Error::EncryptionFailed { label: LABEL })?;
zuc128_xor_inplace(self.keys.cipher(), &iv, &mut buffer, bit_len);
Ok(etm::pack(&iv, &buffer, self.keys.mac()))
}
fn decrypt(&self, ciphertext: &[u8]) -> Result<Vec<u8>> {
let (iv, body) = etm::unpack_and_verify(ciphertext, IV_LEN, self.keys.mac(), LABEL)?;
let iv: &[u8; IV_LEN] = iv.try_into().expect("iv length was verified");
let mut buffer = body.to_vec();
let bit_len = bit_length(&buffer).ok_or(Error::DecryptionFailed { label: LABEL })?;
zuc128_xor_inplace(self.keys.cipher(), iv, &mut buffer, bit_len);
Ok(buffer)
}
}
fn bit_length(data: &[u8]) -> Option<usize> {
data.len().checked_mul(8)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::error::Error;
#[test]
fn matches_gb_standard_keystream_vector() {
let mut buffer = [0u8; 8];
zuc128_xor_inplace(&[0u8; 16], &[0u8; 16], &mut buffer, 64);
assert_eq!(buffer, [0x27, 0xbe, 0xde, 0x74, 0x01, 0x80, 0x82, 0xda]);
}
#[test]
fn roundtrip_including_partial_word_tail() {
let encryptor = Zuc128Encryptor::new([3u8; 16]);
for plaintext in [
b"".as_slice(),
b"a",
b"abc",
b"abcd",
b"abcde",
&[0xffu8; 40],
] {
let blob = encryptor.encrypt(plaintext).unwrap();
assert_eq!(encryptor.decrypt(&blob).unwrap(), plaintext);
}
}
#[test]
fn ciphertext_differs_from_plaintext_and_tamper_fails() {
let encryptor = Zuc128Encryptor::new([3u8; 16]);
let blob = encryptor.encrypt(b"0123456789").unwrap();
assert_ne!(&blob[50..], b"0123456789");
let mut tampered = blob.clone();
let last = tampered.len() - 1;
tampered[last] ^= 0x01;
assert_eq!(
encryptor.decrypt(&tampered).unwrap_err(),
Error::MacVerificationFailed { label: LABEL }
);
}
}