use crate::aes::Aes128;
fn validate_params(nonce_len: usize, tag_len: usize) -> Result<usize, crate::Error> {
if !(7..=13).contains(&nonce_len) || !(4..=16).contains(&tag_len) || !tag_len.is_multiple_of(2)
{
return Err(crate::Error::InvalidInput);
}
Ok(15 - nonce_len)
}
fn b0_flags(tag_len: usize, has_aad: bool, l: usize) -> u8 {
((((tag_len - 2) / 2) << 3) | (u8::from(has_aad) << 6) as usize | (l - 1)) as u8
}
fn check_length_domain(l: usize, pt_len: usize, aad_len: usize) -> Result<(), crate::Error> {
if l < 8 && pt_len >= 1usize << (8 * l) {
return Err(crate::Error::InvalidInput);
}
if aad_len >= 0xff00 {
return Err(crate::Error::InvalidInput);
}
Ok(())
}
fn ctr_raw(nonce: &[u8], l: usize, counter: u32) -> [u8; 16] {
debug_assert!(1 + nonce.len() + l == 16);
let mut block = [0u8; 16];
block[0] = l as u8 - 1;
block[1..1 + nonce.len()].copy_from_slice(nonce);
let ctr = counter as u64;
for i in 0..l {
block[16 - l + i] = (ctr >> (8 * (l - 1 - i))) as u8;
}
block
}
fn ctr_block(aes: &Aes128, nonce: &[u8], l: usize, counter: u32) -> [u8; 16] {
let mut block = ctr_raw(nonce, l, counter);
aes.encrypt_block(&mut block);
block
}
fn ctr_xor(aes: &Aes128, nonce: &[u8], l: usize, start: u32, data: &[u8]) -> Vec<u8> {
let mut out = vec![0u8; data.len()];
let mut counter = start;
let mut ks = [0u8; crate::aes::CTR_BATCH_BLOCKS * 16];
for (in_chunk, out_chunk) in data
.chunks(crate::aes::CTR_BATCH_BLOCKS * 16)
.zip(out.chunks_mut(crate::aes::CTR_BATCH_BLOCKS * 16))
{
let n = in_chunk.len().div_ceil(16);
aes.encrypt_ctr_batch(ctr_raw(nonce, l, counter), n, &mut ks);
let ks_slice = &ks[..out_chunk.len()];
for (o, (b, k)) in out_chunk.iter_mut().zip(in_chunk.iter().zip(ks_slice)) {
*o = b ^ k;
}
counter = counter.wrapping_add(n as u32);
}
out
}
fn cbc_mac(
aes: &Aes128,
nonce: &[u8],
l: usize,
tag_len: usize,
aad: &[u8],
pt: &[u8],
) -> [u8; 16] {
let mut buf: Vec<u8> = Vec::with_capacity(16 + aad.len() + pt.len() + 32);
buf.push(b0_flags(tag_len, !aad.is_empty(), l));
buf.extend_from_slice(nonce);
let plen = pt.len() as u64;
for i in (0..l).rev() {
buf.push((plen >> (8 * i)) as u8);
}
if !aad.is_empty() {
buf.extend_from_slice(&(aad.len() as u16).to_be_bytes());
buf.extend_from_slice(aad);
let rem = buf.len() % 16;
if rem != 0 {
buf.resize(buf.len() + 16 - rem, 0);
}
}
buf.extend_from_slice(pt);
let mut t = [0u8; 16];
for chunk in buf.chunks(16) {
let mut block = [0u8; 16];
block[..chunk.len()].copy_from_slice(chunk);
for i in 0..16 {
block[i] ^= t[i];
}
aes.encrypt_block(&mut block);
t = block;
}
t
}
fn tag_finish(t: &mut [u8; 16], s0: &[u8; 16], tag_len: usize) {
for i in 0..tag_len {
t[i] ^= s0[i];
}
}
fn seal_core(
aes: &Aes128,
nonce: &[u8],
tag_len: usize,
aad: &[u8],
plaintext: &[u8],
) -> Result<Vec<u8>, crate::Error> {
let l = validate_params(nonce.len(), tag_len)?;
check_length_domain(l, plaintext.len(), aad.len())?;
let mut t = cbc_mac(aes, nonce, l, tag_len, aad, plaintext);
let ct = ctr_xor(aes, nonce, l, 1, plaintext);
let s0 = ctr_block(aes, nonce, l, 0);
tag_finish(&mut t, &s0, tag_len);
let mut out = ct;
out.extend_from_slice(&t[..tag_len]);
Ok(out)
}
fn open_core(
aes: &Aes128,
nonce: &[u8],
tag_len: usize,
aad: &[u8],
ct_and_tag: &[u8],
) -> Result<Vec<u8>, crate::Error> {
let l = validate_params(nonce.len(), tag_len)?;
if ct_and_tag.len() < tag_len {
return Err(crate::Error::VerificationFailed);
}
if l < 8 && ct_and_tag.len() - tag_len >= 1usize << (8 * l) {
return Err(crate::Error::VerificationFailed);
}
let split = ct_and_tag.len() - tag_len;
let (ct, tag) = ct_and_tag.split_at(split);
let pt = ctr_xor(aes, nonce, l, 1, ct);
let mut t = cbc_mac(aes, nonce, l, tag_len, aad, &pt);
let s0 = ctr_block(aes, nonce, l, 0);
tag_finish(&mut t, &s0, tag_len);
crate::ct::verify_tag(&t[..tag_len], tag)?;
Ok(pt)
}
#[derive(Clone)]
pub struct Aes128CcmAny {
aes: Aes128,
tag_len: usize,
}
impl Aes128CcmAny {
pub const KEY_LEN: usize = 16;
pub const APPROVAL: crate::Approval = crate::Approval::Approved;
pub fn new(key: &[u8; 16], tag_len: usize) -> Result<Self, crate::Error> {
if !(4..=16).contains(&tag_len) || !tag_len.is_multiple_of(2) {
return Err(crate::Error::InvalidInput);
}
Ok(Self {
aes: Aes128::new(key),
tag_len,
})
}
pub fn seal(
&self,
nonce: &[u8],
aad: &[u8],
plaintext: &[u8],
) -> Result<Vec<u8>, crate::Error> {
seal_core(&self.aes, nonce, self.tag_len, aad, plaintext)
}
pub fn open(
&self,
nonce: &[u8],
aad: &[u8],
ct_and_tag: &[u8],
) -> Result<Vec<u8>, crate::Error> {
open_core(&self.aes, nonce, self.tag_len, aad, ct_and_tag)
}
}
impl std::fmt::Debug for Aes128CcmAny {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Aes128CcmAny")
.field("tag_len", &self.tag_len)
.finish()
}
}
macro_rules! ccm_impl {
($name:ident, $nonce_len:expr, $tag_len:expr, $doc:expr) => {
#[doc = $doc]
#[derive(Clone)]
pub struct $name {
any: Aes128CcmAny,
}
impl $name {
pub const KEY_LEN: usize = 16;
pub const NONCE_LEN: usize = $nonce_len;
pub const TAG_LEN: usize = $tag_len;
pub const APPROVAL: crate::Approval = crate::Approval::Approved;
pub fn new(key: &[u8; 16]) -> Self {
Self {
any: Aes128CcmAny::new(key, $tag_len).expect("fixed parameter set"),
}
}
pub fn seal(
&self,
nonce: &[u8; $nonce_len],
aad: &[u8],
plaintext: &[u8],
) -> Result<Vec<u8>, crate::Error> {
self.any.seal(nonce, aad, plaintext)
}
pub fn open(
&self,
nonce: &[u8; $nonce_len],
aad: &[u8],
ct_and_tag: &[u8],
) -> Result<Vec<u8>, crate::Error> {
self.any.open(nonce, aad, ct_and_tag)
}
}
impl std::fmt::Debug for $name {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(stringify!($name))
}
}
};
}
ccm_impl!(
Aes128Ccm,
13,
16,
"AES-128-CCM 实例(M=16,13 字节 nonce,L=2)。"
);
ccm_impl!(
Aes128CcmTls,
12,
16,
"AES-128-CCM 实例(M=16,12 字节 nonce,L=3)——RFC 8446 §B.5 TLS 1.3 参数集。"
);
ccm_impl!(
Aes128Ccm8Tls,
12,
8,
"AES-128-CCM 实例(M=8,12 字节 nonce,L=3)——RFC 8446 §B.5 的 \
AEAD_AES_128_CCM_8;8 字节标签不在 SP 800-52r2 TLS 批准套件面。"
);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn round_trip_and_tamper() {
let key = [0x07u8; 16];
let nonce13 = [
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c,
];
let aead = Aes128Ccm::new(&key);
for len in [0usize, 1, 15, 16, 17, 33, 64] {
let pt: Vec<u8> = (0..len).map(|i| i as u8).collect();
let sealed = aead.seal(&nonce13, b"aad", &pt).unwrap();
assert_eq!(sealed.len(), len + 16);
let opened = aead.open(&nonce13, b"aad", &sealed).expect("round trip");
assert_eq!(opened, pt, "len {len}");
}
let sealed = aead.seal(&nonce13, b"aad", b"hello ccm").unwrap();
let mut bad = sealed.clone();
let last = bad.len() - 1;
bad[last] ^= 1;
assert_eq!(
aead.open(&nonce13, b"aad", &bad),
Err(crate::Error::VerificationFailed)
);
assert_eq!(
aead.open(&nonce13, b"bad", &sealed),
Err(crate::Error::VerificationFailed)
);
let nonce12 = [
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x1b,
];
let tls = Aes128CcmTls::new(&key);
for len in [0usize, 1, 15, 16, 17, 33, 64] {
let pt: Vec<u8> = (0..len).map(|i| i as u8).collect();
let sealed = tls.seal(&nonce12, b"aad", &pt).unwrap();
assert_eq!(sealed.len(), len + 16);
let opened = tls.open(&nonce12, b"aad", &sealed).expect("round trip");
assert_eq!(opened, pt, "tls len {len}");
}
assert_ne!(
aead.seal(&nonce13, b"aad", b"x").unwrap(),
tls.seal(&nonce12, b"aad", b"x").unwrap()
);
let ccm8 = Aes128Ccm8Tls::new(&key);
let any8 = Aes128CcmAny::new(&key, 8).unwrap();
for len in [0usize, 1, 15, 16, 17, 33, 64] {
let pt: Vec<u8> = (0..len).map(|i| i as u8).collect();
let sealed = ccm8.seal(&nonce12, b"aad", &pt).unwrap();
assert_eq!(sealed.len(), len + 8);
let opened = ccm8.open(&nonce12, b"aad", &sealed).expect("round trip");
assert_eq!(opened, pt, "ccm8 len {len}");
assert_eq!(
sealed,
any8.seal(nonce12.as_slice(), b"aad", &pt).unwrap(),
"fixed Ccm8Tls must match Any(M=8), len {len}"
);
}
let big = vec![0u8; 1 << 16];
assert_eq!(
aead.seal(&nonce13, b"", &big),
Err(crate::Error::InvalidInput)
);
assert_eq!(
aead.seal(&nonce13, &[0u8; 0xff00], &[0u8; 16]),
Err(crate::Error::InvalidInput)
);
assert_eq!(
tls.open(&nonce12, b"", &vec![0u8; 16 + (1 << 24)]),
Err(crate::Error::VerificationFailed)
);
}
#[test]
fn any_param_validation() {
let key = [0x11u8; 16];
for bad in [2usize, 18, 9, 5, 0] {
assert!(
matches!(
Aes128CcmAny::new(&key, bad),
Err(crate::Error::InvalidInput)
),
"tag_len {bad} must be rejected"
);
}
for m in [4usize, 6, 8, 10, 12, 14, 16] {
assert!(Aes128CcmAny::new(&key, m).is_ok(), "tag_len {m}");
}
let any = Aes128CcmAny::new(&key, 8).unwrap();
for bad_nonce in [vec![0u8; 6], vec![0u8; 14]] {
assert_eq!(
any.seal(&bad_nonce, b"", b"pt"),
Err(crate::Error::InvalidInput),
"nonce len {} must be rejected",
bad_nonce.len()
);
assert_eq!(
any.open(&bad_nonce, b"", &[0u8; 24]),
Err(crate::Error::InvalidInput),
"nonce len {} must be rejected on open",
bad_nonce.len()
);
}
for n in 7usize..=13 {
let nonce = vec![0xa0u8; n];
let sealed = any.seal(&nonce, b"aad", b"payload").unwrap();
assert_eq!(sealed.len(), 7 + 8);
assert_eq!(any.open(&nonce, b"aad", &sealed).unwrap(), b"payload");
}
let nonce12 = [0u8; 12];
assert_eq!(
any.open(&nonce12, b"", &[0u8; 7]),
Err(crate::Error::VerificationFailed)
);
}
}