use aes::Aes128;
use ctr::Ctr128BE;
use ctr::cipher::{KeyIvInit, StreamCipher};
pub struct VideoCipher {
cipher: Ctr128BE<Aes128>,
leftover: [u8; 16],
leftover_count: usize,
}
impl VideoCipher {
pub fn new(key: &[u8; 16], iv: &[u8; 16]) -> Self {
Self {
cipher: Ctr128BE::<Aes128>::new(key.into(), iv.into()),
leftover: [0u8; 16],
leftover_count: 0,
}
}
pub fn decrypt(&mut self, payload: &mut [u8]) {
let n = self.leftover_count;
if n > 0 {
let apply = n.min(payload.len());
for (p, &k) in payload[..apply].iter_mut().zip(&self.leftover[(16 - n)..]) {
*p ^= k;
}
if apply < n {
self.leftover_count = n - apply;
return;
}
}
let offset = n;
let remaining = payload.len() - offset;
let full_len = (remaining / 16) * 16;
self.cipher.apply_keystream(&mut payload[offset..offset + full_len]);
let rest_len = remaining % 16;
if rest_len > 0 {
let rest_start = payload.len() - rest_len;
self.leftover = [0u8; 16];
self.leftover[..rest_len].copy_from_slice(&payload[rest_start..]);
self.cipher.apply_keystream(&mut self.leftover);
payload[rest_start..].copy_from_slice(&self.leftover[..rest_len]);
self.leftover_count = 16 - rest_len;
} else {
self.leftover_count = 0;
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn decrypt_full_blocks() {
let key = [0x01u8; 16];
let iv = [0x00u8; 16];
let mut cipher1 = VideoCipher::new(&key, &iv);
let mut cipher2 = VideoCipher::new(&key, &iv);
let original = vec![0xAA; 32];
let mut data = original.clone();
cipher1.decrypt(&mut data); assert_ne!(data, original);
cipher2.decrypt(&mut data); assert_eq!(data, original);
}
#[test]
fn decrypt_partial_blocks_streaming() {
let key = [0x42u8; 16];
let iv = [0x00u8; 16];
let mut cipher_a = VideoCipher::new(&key, &iv);
let mut chunk1 = vec![0u8; 10];
let mut chunk2 = vec![0u8; 22]; cipher_a.decrypt(&mut chunk1);
cipher_a.decrypt(&mut chunk2);
let mut cipher_b = VideoCipher::new(&key, &iv);
let mut full = vec![0u8; 32];
cipher_b.decrypt(&mut full);
assert_eq!(&chunk1[..], &full[..10]);
assert_eq!(&chunk2[..], &full[10..]);
}
}