use crate::primitives::aes::{aes_encrypt_block, aes_key_expansion};
use crate::primitives::PrimitivesError;
const R_BLOCK: [u8; 16] = [
0xe1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
];
fn right_shift(block: &mut [u8; 16]) {
let mut carry = 0u8;
for byte in block.iter_mut() {
let old_carry = carry;
carry = *byte & 0x01;
*byte >>= 1;
if old_carry != 0 {
*byte |= 0x80;
}
}
}
fn gf128_multiply(block0: &[u8; 16], block1: &[u8; 16]) -> [u8; 16] {
let mut v = *block1;
let mut z = [0u8; 16];
for &b in block0.iter() {
for j in (0..8).rev() {
let bit = (b >> j) & 1;
let mask = (-(bit as i8)) as u8;
for k in 0..16 {
z[k] ^= v[k] & mask;
}
let lsb = v[15] & 1;
let rmask = (-(lsb as i8)) as u8;
right_shift(&mut v);
for k in 0..16 {
v[k] ^= R_BLOCK[k] & rmask;
}
}
}
z
}
fn ghash(h: &[u8; 16], input: &[u8]) -> [u8; 16] {
let mut result = [0u8; 16];
for i in (0..input.len()).step_by(16) {
let mut block = result;
let remaining = std::cmp::min(16, input.len() - i);
for j in 0..16 {
if j < remaining {
block[j] ^= input[i + j];
}
}
result = gf128_multiply(&block, h);
}
result
}
fn build_auth_input_standard(aad: &[u8], ciphertext: &[u8]) -> Vec<u8> {
let aad_pad = if aad.is_empty() || aad.len().is_multiple_of(16) {
0
} else {
16 - (aad.len() % 16)
};
let ct_pad = if ciphertext.is_empty() || ciphertext.len().is_multiple_of(16) {
0
} else {
16 - (ciphertext.len() % 16)
};
let total = aad.len() + aad_pad + ciphertext.len() + ct_pad + 16;
let mut out = Vec::with_capacity(total);
out.extend_from_slice(aad);
out.extend(std::iter::repeat_n(0u8, aad_pad));
out.extend_from_slice(ciphertext);
out.extend(std::iter::repeat_n(0u8, ct_pad));
let aad_bits = (aad.len() as u64) * 8;
let ct_bits = (ciphertext.len() as u64) * 8;
out.extend_from_slice(&aad_bits.to_be_bytes());
out.extend_from_slice(&ct_bits.to_be_bytes());
out
}
fn build_auth_input_ts_compat(ciphertext: &[u8]) -> Vec<u8> {
let pad_len = if ciphertext.is_empty() {
16
} else if ciphertext.len().is_multiple_of(16) {
0
} else {
16 - (ciphertext.len() % 16)
};
let total = 16 + ciphertext.len() + pad_len + 16;
let mut out = Vec::with_capacity(total);
out.extend_from_slice(&[0u8; 16]);
out.extend_from_slice(ciphertext);
out.extend(std::iter::repeat_n(0u8, pad_len));
let aad_bits: u64 = 0;
let ct_bits = (ciphertext.len() as u64) * 8;
out.extend_from_slice(&aad_bits.to_be_bytes());
out.extend_from_slice(&ct_bits.to_be_bytes());
out
}
fn increment_counter(block: &[u8; 16]) -> [u8; 16] {
let mut result = *block;
for i in (12..16).rev() {
result[i] = result[i].wrapping_add(1);
if result[i] != 0 {
break;
}
}
result
}
fn gctr(input: &[u8], initial_counter: &[u8; 16], round_keys: &[u32]) -> Vec<u8> {
if input.is_empty() {
return Vec::new();
}
let mut output = Vec::with_capacity(input.len());
let mut counter_block = *initial_counter;
let n = input.len().div_ceil(16);
let mut pos = 0;
for i in 0..n {
let encrypted_counter = aes_encrypt_block(&counter_block, round_keys);
let chunk = std::cmp::min(16, input.len() - pos);
for (j, enc_byte) in encrypted_counter.iter().enumerate().take(chunk) {
output.push(input[pos + j] ^ enc_byte);
}
pos += chunk;
if i + 1 < n {
counter_block = increment_counter(&counter_block);
}
}
output
}
fn derive_j0(h: &[u8; 16], iv: &[u8]) -> [u8; 16] {
if iv.len() == 12 {
let mut j0 = [0u8; 16];
j0[..12].copy_from_slice(iv);
j0[15] = 0x01;
j0
} else {
let mut padded = Vec::from(iv);
let pad_len = if iv.len().is_multiple_of(16) {
0
} else {
16 - (iv.len() % 16)
};
padded.extend(std::iter::repeat_n(0u8, pad_len));
padded.extend_from_slice(&[0u8; 8]);
let iv_bits = (iv.len() as u64) * 8;
padded.extend_from_slice(&iv_bits.to_be_bytes());
ghash(h, &padded)
}
}
pub fn aes_gcm_encrypt(
key: &[u8],
iv: &[u8],
plaintext: &[u8],
aad: &[u8],
) -> Result<Vec<u8>, PrimitivesError> {
if key.len() != 16 && key.len() != 32 {
return Err(PrimitivesError::InvalidLength(format!(
"AES key must be 16 or 32 bytes, got {}",
key.len()
)));
}
if iv.is_empty() {
return Err(PrimitivesError::InvalidLength(
"IV must not be empty".to_string(),
));
}
let round_keys = aes_key_expansion(key)?;
let h = aes_encrypt_block(&[0u8; 16], &round_keys);
let j0 = derive_j0(&h, iv);
let ctr_start = increment_counter(&j0);
let ciphertext = gctr(plaintext, &ctr_start, &round_keys);
let auth_input = build_auth_input_standard(aad, &ciphertext);
let s = ghash(&h, &auth_input);
let tag_bytes = gctr(&s, &j0, &round_keys);
let mut tag = [0u8; 16];
tag.copy_from_slice(&tag_bytes);
let mut result = ciphertext;
result.extend_from_slice(&tag);
Ok(result)
}
pub fn aes_gcm_decrypt(
key: &[u8],
iv: &[u8],
ciphertext_with_tag: &[u8],
aad: &[u8],
) -> Result<Vec<u8>, PrimitivesError> {
if key.len() != 16 && key.len() != 32 {
return Err(PrimitivesError::InvalidLength(format!(
"AES key must be 16 or 32 bytes, got {}",
key.len()
)));
}
if iv.is_empty() {
return Err(PrimitivesError::InvalidLength(
"IV must not be empty".to_string(),
));
}
if ciphertext_with_tag.len() < 16 {
return Err(PrimitivesError::DecryptionFailed);
}
let ct_len = ciphertext_with_tag.len() - 16;
let ciphertext = &ciphertext_with_tag[..ct_len];
let provided_tag = &ciphertext_with_tag[ct_len..];
let round_keys = aes_key_expansion(key)?;
let h = aes_encrypt_block(&[0u8; 16], &round_keys);
let j0 = derive_j0(&h, iv);
let auth_input = build_auth_input_standard(aad, ciphertext);
let s = ghash(&h, &auth_input);
let computed_tag_bytes = gctr(&s, &j0, &round_keys);
let mut diff = 0u8;
for i in 0..16 {
diff |= computed_tag_bytes[i] ^ provided_tag[i];
}
if diff != 0 {
return Err(PrimitivesError::DecryptionFailed);
}
let ctr_start = increment_counter(&j0);
let plaintext = gctr(ciphertext, &ctr_start, &round_keys);
Ok(plaintext)
}
pub fn aes_gcm_encrypt_ts_compat(
key: &[u8],
iv: &[u8],
plaintext: &[u8],
) -> Result<Vec<u8>, PrimitivesError> {
if key.len() != 16 && key.len() != 32 {
return Err(PrimitivesError::InvalidLength(format!(
"AES key must be 16 or 32 bytes, got {}",
key.len()
)));
}
if iv.is_empty() {
return Err(PrimitivesError::InvalidLength(
"IV must not be empty".to_string(),
));
}
let round_keys = aes_key_expansion(key)?;
let h = aes_encrypt_block(&[0u8; 16], &round_keys);
let j0 = derive_j0(&h, iv);
let ctr_start = increment_counter(&j0);
let ciphertext = gctr(plaintext, &ctr_start, &round_keys);
let auth_input = build_auth_input_ts_compat(&ciphertext);
let s = ghash(&h, &auth_input);
let tag_bytes = gctr(&s, &j0, &round_keys);
let mut tag = [0u8; 16];
tag.copy_from_slice(&tag_bytes);
let mut result = ciphertext;
result.extend_from_slice(&tag);
Ok(result)
}
pub fn aes_gcm_decrypt_ts_compat(
key: &[u8],
iv: &[u8],
ciphertext_with_tag: &[u8],
) -> Result<Vec<u8>, PrimitivesError> {
if key.len() != 16 && key.len() != 32 {
return Err(PrimitivesError::InvalidLength(format!(
"AES key must be 16 or 32 bytes, got {}",
key.len()
)));
}
if iv.is_empty() {
return Err(PrimitivesError::InvalidLength(
"IV must not be empty".to_string(),
));
}
if ciphertext_with_tag.len() < 16 {
return Err(PrimitivesError::DecryptionFailed);
}
let ct_len = ciphertext_with_tag.len() - 16;
let ciphertext = &ciphertext_with_tag[..ct_len];
let provided_tag = &ciphertext_with_tag[ct_len..];
let round_keys = aes_key_expansion(key)?;
let h = aes_encrypt_block(&[0u8; 16], &round_keys);
let j0 = derive_j0(&h, iv);
let auth_input = build_auth_input_ts_compat(ciphertext);
let s = ghash(&h, &auth_input);
let computed_tag_bytes = gctr(&s, &j0, &round_keys);
let mut diff = 0u8;
for i in 0..16 {
diff |= computed_tag_bytes[i] ^ provided_tag[i];
}
if diff != 0 {
return Err(PrimitivesError::DecryptionFailed);
}
let ctr_start = increment_counter(&j0);
let plaintext = gctr(ciphertext, &ctr_start, &round_keys);
Ok(plaintext)
}
#[cfg(test)]
mod tests {
use super::*;
fn hex_to_bytes(hex: &str) -> Vec<u8> {
(0..hex.len())
.step_by(2)
.map(|i| u8::from_str_radix(&hex[i..i + 2], 16).unwrap())
.collect()
}
fn bytes_to_hex(bytes: &[u8]) -> String {
bytes.iter().map(|b| format!("{:02x}", b)).collect()
}
#[test]
fn test_nist_aes128_gcm_test_case_2() {
let key = hex_to_bytes("00000000000000000000000000000000");
let iv = hex_to_bytes("000000000000000000000000");
let pt = hex_to_bytes("00000000000000000000000000000000");
let expected_ct = hex_to_bytes("0388dace60b6a392f328c2b971b2fe78");
let expected_tag = hex_to_bytes("ab6e47d42cec13bdf53a67b21257bddf");
let result = aes_gcm_encrypt(&key, &iv, &pt, &[]).unwrap();
let ct = &result[..result.len() - 16];
let tag = &result[result.len() - 16..];
assert_eq!(
bytes_to_hex(ct),
bytes_to_hex(&expected_ct),
"NIST Test Case 2 ciphertext mismatch"
);
assert_eq!(
bytes_to_hex(tag),
bytes_to_hex(&expected_tag),
"NIST Test Case 2 tag mismatch"
);
}
#[test]
fn test_nist_aes128_gcm_test_case_3() {
let key = hex_to_bytes("feffe9928665731c6d6a8f9467308308");
let iv = hex_to_bytes("cafebabefacedbaddecaf888");
let pt = hex_to_bytes("d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b391aafd255");
let expected_ct = hex_to_bytes("42831ec2217774244b7221b784d0d49ce3aa212f2c02a4e035c17e2329aca12e21d514b25466931c7d8f6a5aac84aa051ba30b396a0aac973d58e091473f5985");
let expected_tag = hex_to_bytes("4d5c2af327cd64a62cf35abd2ba6fab4");
let result = aes_gcm_encrypt(&key, &iv, &pt, &[]).unwrap();
let ct = &result[..result.len() - 16];
let tag = &result[result.len() - 16..];
assert_eq!(
bytes_to_hex(ct),
bytes_to_hex(&expected_ct),
"NIST Test Case 3 ciphertext mismatch"
);
assert_eq!(
bytes_to_hex(tag),
bytes_to_hex(&expected_tag),
"NIST Test Case 3 tag mismatch"
);
}
#[test]
fn test_nist_aes128_gcm_test_case_4_with_aad() {
let key = hex_to_bytes("feffe9928665731c6d6a8f9467308308");
let iv = hex_to_bytes("cafebabefacedbaddecaf888");
let pt = hex_to_bytes("d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b39");
let aad = hex_to_bytes("feedfacedeadbeeffeedfacedeadbeefabaddad2");
let expected_ct = hex_to_bytes("42831ec2217774244b7221b784d0d49ce3aa212f2c02a4e035c17e2329aca12e21d514b25466931c7d8f6a5aac84aa051ba30b396a0aac973d58e091");
let expected_tag = hex_to_bytes("5bc94fbc3221a5db94fae95ae7121a47");
let result = aes_gcm_encrypt(&key, &iv, &pt, &aad).unwrap();
let ct = &result[..result.len() - 16];
let tag = &result[result.len() - 16..];
assert_eq!(
bytes_to_hex(ct),
bytes_to_hex(&expected_ct),
"NIST Test Case 4 ciphertext mismatch"
);
assert_eq!(
bytes_to_hex(tag),
bytes_to_hex(&expected_tag),
"NIST Test Case 4 tag mismatch"
);
}
#[test]
fn test_nist_aes256_gcm_test_case_14() {
let key = hex_to_bytes("0000000000000000000000000000000000000000000000000000000000000000");
let iv = hex_to_bytes("000000000000000000000000");
let pt = hex_to_bytes("00000000000000000000000000000000");
let expected_ct = hex_to_bytes("cea7403d4d606b6e074ec5d3baf39d18");
let expected_tag = hex_to_bytes("d0d1c8a799996bf0265b98b5d48ab919");
let result = aes_gcm_encrypt(&key, &iv, &pt, &[]).unwrap();
let ct = &result[..result.len() - 16];
let tag = &result[result.len() - 16..];
assert_eq!(
bytes_to_hex(ct),
bytes_to_hex(&expected_ct),
"NIST Test Case 14 ciphertext mismatch"
);
assert_eq!(
bytes_to_hex(tag),
bytes_to_hex(&expected_tag),
"NIST Test Case 14 tag mismatch"
);
}
#[test]
fn test_nist_aes256_gcm_test_case_16_with_aad() {
let key = hex_to_bytes("feffe9928665731c6d6a8f9467308308feffe9928665731c6d6a8f9467308308");
let iv = hex_to_bytes("cafebabefacedbaddecaf888");
let pt = hex_to_bytes("d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b39");
let aad = hex_to_bytes("feedfacedeadbeeffeedfacedeadbeefabaddad2");
let expected_ct = hex_to_bytes("522dc1f099567d07f47f37a32a84427d643a8cdcbfe5c0c97598a2bd2555d1aa8cb08e48590dbb3da7b08b1056828838c5f61e6393ba7a0abcc9f662");
let expected_tag = hex_to_bytes("76fc6ece0f4e1768cddf8853bb2d551b");
let result = aes_gcm_encrypt(&key, &iv, &pt, &aad).unwrap();
let ct = &result[..result.len() - 16];
let tag = &result[result.len() - 16..];
assert_eq!(
bytes_to_hex(ct),
bytes_to_hex(&expected_ct),
"NIST Test Case 16 ciphertext mismatch"
);
assert_eq!(
bytes_to_hex(tag),
bytes_to_hex(&expected_tag),
"NIST Test Case 16 tag mismatch"
);
}
#[test]
fn test_aes_gcm_roundtrip_12byte_iv() {
let key = hex_to_bytes("feffe9928665731c6d6a8f9467308308");
let iv = hex_to_bytes("cafebabefacedbaddecaf888");
let plaintext = b"Hello, AES-GCM!";
let encrypted = aes_gcm_encrypt(&key, &iv, plaintext, &[]).unwrap();
let decrypted = aes_gcm_decrypt(&key, &iv, &encrypted, &[]).unwrap();
assert_eq!(decrypted, plaintext);
}
#[test]
fn test_aes_gcm_roundtrip_with_aad() {
let key = hex_to_bytes("feffe9928665731c6d6a8f9467308308");
let iv = hex_to_bytes("cafebabefacedbaddecaf888");
let plaintext = b"Hello, AES-GCM with AAD!";
let aad = b"additional data";
let encrypted = aes_gcm_encrypt(&key, &iv, plaintext, aad).unwrap();
let decrypted = aes_gcm_decrypt(&key, &iv, &encrypted, aad).unwrap();
assert_eq!(decrypted, plaintext);
}
#[test]
fn test_aes_gcm_roundtrip_32byte_iv() {
let key = hex_to_bytes("feffe9928665731c6d6a8f9467308308feffe9928665731c6d6a8f9467308308");
let iv = hex_to_bytes("cafebabefacedbaddecaf888cafebabefacedbaddecaf888cafebabefacedbad");
let plaintext = b"Testing 32-byte IV for TS SDK compatibility";
let encrypted = aes_gcm_encrypt(&key, &iv, plaintext, &[]).unwrap();
let decrypted = aes_gcm_decrypt(&key, &iv, &encrypted, &[]).unwrap();
assert_eq!(decrypted, plaintext);
}
#[test]
fn test_aes_gcm_roundtrip_empty_plaintext() {
let key = hex_to_bytes("feffe9928665731c6d6a8f9467308308");
let iv = hex_to_bytes("cafebabefacedbaddecaf888");
let aad = hex_to_bytes("feedfacedeadbeef");
let encrypted = aes_gcm_encrypt(&key, &iv, &[], &aad).unwrap();
assert_eq!(encrypted.len(), 16); let decrypted = aes_gcm_decrypt(&key, &iv, &encrypted, &aad).unwrap();
assert!(decrypted.is_empty());
}
#[test]
fn test_aes_gcm_tampered_ciphertext() {
let key = hex_to_bytes("feffe9928665731c6d6a8f9467308308");
let iv = hex_to_bytes("cafebabefacedbaddecaf888");
let plaintext = b"Hello, AES-GCM!";
let mut encrypted = aes_gcm_encrypt(&key, &iv, plaintext, &[]).unwrap();
encrypted[0] ^= 0x01;
let result = aes_gcm_decrypt(&key, &iv, &encrypted, &[]);
assert!(result.is_err());
match result.unwrap_err() {
PrimitivesError::DecryptionFailed => {}
e => panic!("Expected DecryptionFailed, got {:?}", e),
}
}
#[test]
fn test_aes_gcm_tampered_tag() {
let key = hex_to_bytes("feffe9928665731c6d6a8f9467308308");
let iv = hex_to_bytes("cafebabefacedbaddecaf888");
let plaintext = b"Hello, AES-GCM!";
let mut encrypted = aes_gcm_encrypt(&key, &iv, plaintext, &[]).unwrap();
let len = encrypted.len();
encrypted[len - 1] ^= 0x01;
let result = aes_gcm_decrypt(&key, &iv, &encrypted, &[]);
assert!(result.is_err());
}
#[test]
fn test_aes_gcm_wrong_aad() {
let key = hex_to_bytes("feffe9928665731c6d6a8f9467308308");
let iv = hex_to_bytes("cafebabefacedbaddecaf888");
let plaintext = b"Hello, AES-GCM!";
let aad = b"correct aad";
let encrypted = aes_gcm_encrypt(&key, &iv, plaintext, aad).unwrap();
let result = aes_gcm_decrypt(&key, &iv, &encrypted, b"wrong aad");
assert!(result.is_err());
}
#[test]
fn test_aes_gcm_ts_compat_roundtrip() {
let key = hex_to_bytes("feffe9928665731c6d6a8f9467308308feffe9928665731c6d6a8f9467308308");
let iv = hex_to_bytes("cafebabefacedbaddecaf888cafebabefacedbaddecaf888cafebabefacedbad");
let plaintext = b"TS SDK compatible encryption";
let encrypted = aes_gcm_encrypt_ts_compat(&key, &iv, plaintext).unwrap();
let decrypted = aes_gcm_decrypt_ts_compat(&key, &iv, &encrypted).unwrap();
assert_eq!(decrypted, plaintext);
}
#[test]
fn test_aes_gcm_ts_compat_tampered() {
let key = hex_to_bytes("feffe9928665731c6d6a8f9467308308feffe9928665731c6d6a8f9467308308");
let iv = hex_to_bytes("cafebabefacedbaddecaf888cafebabefacedbaddecaf888cafebabefacedbad");
let plaintext = b"TS SDK compatible encryption";
let mut encrypted = aes_gcm_encrypt_ts_compat(&key, &iv, plaintext).unwrap();
encrypted[0] ^= 0x01;
let result = aes_gcm_decrypt_ts_compat(&key, &iv, &encrypted);
assert!(result.is_err());
}
#[test]
fn test_ghash_zero_input() {
let h = [0u8; 16];
let input = [0u8; 16];
let result = ghash(&h, &input);
assert_eq!(result, [0u8; 16]);
}
#[test]
fn test_gf128_multiply_identity() {
let a = [0x01u8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
let b = [0u8; 16];
let result = gf128_multiply(&a, &b);
assert_eq!(result, [0u8; 16]);
}
#[test]
fn test_counter_increment() {
let counter = [0u8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1];
let next = increment_counter(&counter);
assert_eq!(next[15], 2);
assert_eq!(next[14], 0);
}
#[test]
fn test_counter_increment_wrap() {
let counter = [0u8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xFF, 0xFF, 0xFF, 0xFF];
let next = increment_counter(&counter);
assert_eq!(next[12], 0);
assert_eq!(next[13], 0);
assert_eq!(next[14], 0);
assert_eq!(next[15], 0);
assert_eq!(next[11], 0);
}
#[test]
fn test_aes_gcm_invalid_key_length() {
let result = aes_gcm_encrypt(&[0u8; 24], &[0u8; 12], b"test", &[]);
assert!(result.is_err());
}
#[test]
fn test_aes_gcm_empty_iv() {
let result = aes_gcm_encrypt(&[0u8; 16], &[], b"test", &[]);
assert!(result.is_err());
}
#[test]
fn test_aes_gcm_decrypt_too_short() {
let result = aes_gcm_decrypt(&[0u8; 16], &[0u8; 12], &[0u8; 15], &[]);
assert!(result.is_err());
}
#[test]
fn test_aes_gcm_various_lengths() {
let key = hex_to_bytes("feffe9928665731c6d6a8f9467308308");
let iv = hex_to_bytes("cafebabefacedbaddecaf888");
for len in [1, 15, 16, 17, 31, 32, 33, 64, 100] {
let plaintext: Vec<u8> = (0..len).map(|i| (i & 0xff) as u8).collect();
let encrypted = aes_gcm_encrypt(&key, &iv, &plaintext, &[]).unwrap();
assert_eq!(
encrypted.len(),
plaintext.len() + 16,
"Encrypted length wrong for pt len {}",
len
);
let decrypted = aes_gcm_decrypt(&key, &iv, &encrypted, &[]).unwrap();
assert_eq!(
decrypted, plaintext,
"Round-trip failed for plaintext length {}",
len
);
}
}
#[test]
fn test_aes256_gcm_with_32byte_iv_various() {
let key = hex_to_bytes("feffe9928665731c6d6a8f9467308308feffe9928665731c6d6a8f9467308308");
let iv = hex_to_bytes("cafebabefacedbaddecaf888cafebabefacedbaddecaf888cafebabefacedbad");
for len in [0, 1, 16, 32, 48, 64] {
let plaintext: Vec<u8> = (0..len).map(|i| (i & 0xff) as u8).collect();
let encrypted = aes_gcm_encrypt(&key, &iv, &plaintext, &[]).unwrap();
let decrypted = aes_gcm_decrypt(&key, &iv, &encrypted, &[]).unwrap();
assert_eq!(
decrypted, plaintext,
"32-byte IV round-trip failed for pt len {}",
len
);
}
}
}