use aes::cipher::generic_array::GenericArray;
use aes::cipher::{BlockEncrypt, KeyInit};
use aes::Aes128;
use sheathe_core::{Error, Result};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Scheme {
Cenc,
Cbcs,
}
impl Scheme {
pub fn scheme_type(self) -> [u8; 4] {
match self {
Scheme::Cenc => *b"cenc",
Scheme::Cbcs => *b"cbcs",
}
}
}
#[derive(Debug, Clone)]
pub struct ContentKey {
pub kid: [u8; 16],
pub key: [u8; 16],
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Subsample {
pub clear: u32,
pub protected: u32,
}
const CBCS_CRYPT_BLOCKS: usize = 1;
const CBCS_PATTERN_BLOCKS: usize = 10;
pub struct Encryptor {
cipher: Aes128,
}
impl Encryptor {
pub fn new(key: &[u8; 16]) -> Self {
Self {
cipher: Aes128::new(GenericArray::from_slice(key)),
}
}
pub fn encrypt(
&self,
scheme: Scheme,
iv: &[u8; 16],
data: &mut [u8],
subsamples: &[Subsample],
) -> Result<()> {
let total: u64 = subsamples
.iter()
.map(|s| u64::from(s.clear) + u64::from(s.protected))
.sum();
if total != data.len() as u64 {
return Err(Error::malformed("subsample layout does not cover sample"));
}
match scheme {
Scheme::Cenc => self.cenc(iv, data, subsamples),
Scheme::Cbcs => self.cbcs(iv, data, subsamples),
}
Ok(())
}
fn cenc(&self, iv: &[u8; 16], data: &mut [u8], subsamples: &[Subsample]) {
let mut counter = *iv;
let mut keystream = [0u8; 16];
let mut ks_pos = 16usize; let mut off = 0usize;
for s in subsamples {
off += s.clear as usize;
let end = off + s.protected as usize;
while off < end {
if ks_pos == 16 {
keystream = counter;
self.encrypt_block(&mut keystream);
incr_be(&mut counter);
ks_pos = 0;
}
data[off] ^= keystream[ks_pos];
ks_pos += 1;
off += 1;
}
}
}
fn cbcs(&self, iv: &[u8; 16], data: &mut [u8], subsamples: &[Subsample]) {
let mut off = 0usize;
for s in subsamples {
off += s.clear as usize;
let mut remaining = s.protected as usize;
let mut chain = *iv;
let mut block_index = 0usize;
while remaining >= 16 {
if block_index % CBCS_PATTERN_BLOCKS < CBCS_CRYPT_BLOCKS {
let mut block = [0u8; 16];
block.copy_from_slice(&data[off..off + 16]);
for (b, c) in block.iter_mut().zip(chain.iter()) {
*b ^= *c;
}
self.encrypt_block(&mut block);
data[off..off + 16].copy_from_slice(&block);
chain = block;
}
off += 16;
remaining -= 16;
block_index += 1;
}
off += remaining; }
}
fn encrypt_block(&self, block: &mut [u8; 16]) {
let mut ga = GenericArray::clone_from_slice(block);
self.cipher.encrypt_block(&mut ga);
block.copy_from_slice(&ga);
}
}
fn incr_be(counter: &mut [u8; 16]) {
for byte in counter.iter_mut().rev() {
let (v, carry) = byte.overflowing_add(1);
*byte = v;
if !carry {
break;
}
}
}
#[cfg(test)]
mod tests {
use super::*;
const KEY: [u8; 16] = [
0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6, 0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f,
0x3c,
];
fn hex(s: &str) -> Vec<u8> {
(0..s.len())
.step_by(2)
.map(|i| u8::from_str_radix(&s[i..i + 2], 16).unwrap())
.collect()
}
#[test]
fn cenc_matches_nist_ctr_vector() {
let iv = hex("f0f1f2f3f4f5f6f7f8f9fafbfcfdfeff");
let mut data = hex("6bc1bee22e409f96e93d7e117393172a");
let enc = Encryptor::new(&KEY);
let subs = [Subsample {
clear: 0,
protected: 16,
}];
enc.encrypt(Scheme::Cenc, iv[..].try_into().unwrap(), &mut data, &subs)
.unwrap();
assert_eq!(data, hex("874d6191b620e3261bef6864990db6ce"));
}
#[test]
fn cbcs_first_block_matches_nist_cbc_vector() {
let iv = hex("000102030405060708090a0b0c0d0e0f");
let mut data = hex("6bc1bee22e409f96e93d7e117393172a");
let enc = Encryptor::new(&KEY);
let subs = [Subsample {
clear: 0,
protected: 16,
}];
enc.encrypt(Scheme::Cbcs, iv[..].try_into().unwrap(), &mut data, &subs)
.unwrap();
assert_eq!(data, hex("7649abac8119b246cee98e9b12e9197d"));
}
#[test]
fn cenc_leaves_clear_bytes_untouched() {
let iv = [0u8; 16];
let mut data = vec![0xAAu8; 32];
let enc = Encryptor::new(&KEY);
enc.encrypt(
Scheme::Cenc,
&iv,
&mut data,
&[Subsample {
clear: 8,
protected: 24,
}],
)
.unwrap();
assert!(
data[..8].iter().all(|&b| b == 0xAA),
"clear prefix must be untouched"
);
assert!(
data[8..].iter().any(|&b| b != 0xAA),
"protected region must change"
);
}
#[test]
fn cbcs_pattern_skips_blocks() {
let iv = [0u8; 16];
let mut data = vec![0x11u8; 160];
let original = data.clone();
let enc = Encryptor::new(&KEY);
enc.encrypt(
Scheme::Cbcs,
&iv,
&mut data,
&[Subsample {
clear: 0,
protected: 160,
}],
)
.unwrap();
assert_ne!(data[..16], original[..16], "first block encrypted");
assert_eq!(data[16..], original[16..], "blocks 1..9 skipped");
}
#[test]
fn rejects_mismatched_layout() {
let enc = Encryptor::new(&KEY);
let mut data = vec![0u8; 10];
let err = enc.encrypt(
Scheme::Cenc,
&[0u8; 16],
&mut data,
&[Subsample {
clear: 0,
protected: 9,
}],
);
assert!(err.is_err());
}
#[test]
fn cenc_round_trips_across_subsamples() {
let enc = Encryptor::new(&KEY);
let iv = [3u8; 16];
let subs = [
Subsample {
clear: 5,
protected: 20,
},
Subsample {
clear: 10,
protected: 65,
},
];
let original: Vec<u8> = (0..100u8).collect();
let mut data = original.clone();
enc.encrypt(Scheme::Cenc, &iv, &mut data, &subs).unwrap();
assert_ne!(data, original, "ciphertext must differ");
assert_eq!(&data[..5], &original[..5], "leading clear bytes preserved");
enc.encrypt(Scheme::Cenc, &iv, &mut data, &subs).unwrap();
assert_eq!(data, original, "CTR round-trip restores plaintext");
}
}