use crate::aes::{Aes128, Aes256, CTR_BATCH_BLOCKS};
use crate::ops::AeadGcm as _;
use zeroize::Zeroize;
macro_rules! gcm_impl {
(
$name:ident, $inner:ident, $soft:ident, $aes:ident, $factory:ident,
$keylen:literal, $doc:expr
) => {
#[doc = $doc]
pub struct $name {
inner: $inner,
}
enum $inner {
Soft($soft),
Ext(Box<dyn crate::ops::AeadGcm>),
}
impl $name {
pub const KEY_LEN: usize = $keylen;
pub const NONCE_LEN: usize = 12;
pub const TAG_LEN: usize = 16;
pub const APPROVAL: crate::Approval = crate::Approval::Approved;
pub fn new(key: &[u8; $keylen]) -> Self {
let inner = match crate::ops::installed_aead() {
Some(backend) => $inner::Ext(backend.$factory(key)),
None => $inner::Soft($soft::new(key)),
};
Self { inner }
}
pub fn seal(&self, nonce: &[u8; 12], aad: &[u8], plaintext: &[u8]) -> Vec<u8> {
let mut out = plaintext.to_vec();
let tag = match &self.inner {
$inner::Soft(s) => s.seal(nonce, aad, &mut out),
$inner::Ext(c) => c.seal(nonce, aad, &mut out),
};
out.extend_from_slice(&tag);
out
}
pub fn open(
&self,
nonce: &[u8; 12],
aad: &[u8],
ct_and_tag: &[u8],
) -> Result<Vec<u8>, crate::Error> {
if ct_and_tag.len() < 16 {
return Err(crate::Error::VerificationFailed);
}
let split = ct_and_tag.len() - 16;
let (ct, tag_bytes) = ct_and_tag.split_at(split);
let mut pt = ct.to_vec();
let computed = match &self.inner {
$inner::Soft(s) => s.open_compute_tag(nonce, aad, &mut pt),
$inner::Ext(c) => c.open_compute_tag(nonce, aad, &mut pt),
};
if crate::ct::verify_tag(&computed, tag_bytes).is_err() {
pt.zeroize();
return Err(crate::Error::VerificationFailed);
}
Ok(pt)
}
}
impl Clone for $name {
fn clone(&self) -> Self {
Self {
inner: match &self.inner {
$inner::Soft(s) => $inner::Soft(s.clone()),
$inner::Ext(c) => $inner::Ext(c.clone_box()),
},
}
}
}
impl std::fmt::Debug for $name {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(stringify!($name))
}
}
struct $soft {
aes: $aes,
h: u128,
}
impl $soft {
fn new(key: &[u8; $keylen]) -> Self {
let aes = $aes::new(key);
let mut h_block = [0u8; 16];
aes.encrypt_block(&mut h_block);
Self {
aes,
h: u128::from_be_bytes(h_block),
}
}
fn tag_base(&self, nonce: &[u8; 12]) -> u128 {
let mut b = block_j0(nonce).to_be_bytes();
self.aes.encrypt_block(&mut b);
u128::from_be_bytes(b)
}
fn ctr_xor(&self, j0: u128, buf: &mut [u8]) {
let mut ctr = inc32(j0);
let mut ks = [0u8; CTR_BATCH_BLOCKS * 16];
for chunk in buf.chunks_mut(CTR_BATCH_BLOCKS * 16) {
let n = chunk.len().div_ceil(16);
self.aes.encrypt_ctr_batch(ctr.to_be_bytes(), n, &mut ks);
let ks_slice = &ks[..chunk.len()];
for (o, k) in chunk.iter_mut().zip(ks_slice) {
*o ^= k;
}
ctr = (ctr & !0xFFFF_FFFFu128) | (ctr as u32).wrapping_add(n as u32) as u128;
}
}
fn ghash(&self, aad: &[u8], ct: &[u8]) -> u128 {
let block = |c: &[u8]| -> u128 {
let mut b = [0u8; 16];
b[..c.len()].copy_from_slice(c);
u128::from_be_bytes(b)
};
let mut len_b = [0u8; 16];
len_b[..8].copy_from_slice(&((aad.len() as u64) * 8).to_be_bytes());
len_b[8..].copy_from_slice(&((ct.len() as u64) * 8).to_be_bytes());
let mut it = aad
.chunks(16)
.map(|c| block(c))
.chain(ct.chunks(16).map(|c| block(c)))
.chain(std::iter::once(u128::from_be_bytes(len_b)));
let total = aad.len().div_ceil(16) + ct.len().div_ceil(16) + 1;
if total < GHASH_TABLE_MIN_BLOCKS {
let mut y = 0u128;
for x in it {
y = gf128_mul(y ^ x, self.h);
}
y
} else {
let mut tables = GhashTables::build(self.h);
let out = tables.ghash_grouped(&mut it);
tables.zeroize();
out
}
}
}
impl Drop for $soft {
fn drop(&mut self) {
self.h = 0;
}
}
impl Clone for $soft {
fn clone(&self) -> Self {
Self {
aes: self.aes.clone(),
h: self.h,
}
}
}
impl crate::ops::AeadGcm for $soft {
fn seal(&self, nonce: &[u8; 12], aad: &[u8], buf: &mut [u8]) -> [u8; 16] {
let tag_base = self.tag_base(nonce);
self.ctr_xor(block_j0(nonce), buf);
let s = self.ghash(aad, buf);
(tag_base ^ s).to_be_bytes()
}
fn open_compute_tag(&self, nonce: &[u8; 12], aad: &[u8], buf: &mut [u8]) -> [u8; 16] {
let tag_base = self.tag_base(nonce);
let s = self.ghash(aad, buf);
self.ctr_xor(block_j0(nonce), buf);
(tag_base ^ s).to_be_bytes()
}
fn clone_box(&self) -> Box<dyn crate::ops::AeadGcm> {
Box::new(self.clone())
}
}
};
}
fn block_j0(nonce: &[u8; 12]) -> u128 {
let mut b = [0u8; 16];
b[..12].copy_from_slice(nonce);
b[15] = 1;
u128::from_be_bytes(b)
}
fn inc32(block: u128) -> u128 {
let ctr = (block as u32).wrapping_add(1);
(block & !0xFFFF_FFFF) | (ctr as u128)
}
fn gf128_mul(x: u128, y: u128) -> u128 {
const R: u128 = 0xE1u128 << 120;
let mut z: u128 = 0;
let mut v = y;
for i in 0..128 {
let bit = (x >> (127 - i)) & 1;
z ^= v & bit.wrapping_neg();
let lsb = v & 1;
v >>= 1;
v ^= R.wrapping_mul(lsb);
}
z
}
const GHASH_GROUP: usize = 8;
const GHASH_TABLE_MIN_BLOCKS: usize = 64;
fn build_gf128_table(mut g: u128) -> [[u128; 16]; 32] {
const R: u128 = 0xE1u128 << 120;
let mut table = [[0u128; 16]; 32];
for row in &mut table {
for j in 0..4 {
for (m, slot) in row.iter_mut().enumerate() {
if (m >> (3 - j)) & 1 == 1 {
*slot ^= g;
}
}
let lsb = g & 1;
g >>= 1;
g ^= R.wrapping_mul(lsb);
}
}
table
}
#[inline]
fn tbl_mul(table: &[[u128; 16]; 32], x: u128) -> u128 {
let mut z = table[0][((x >> 124) & 0xF) as usize];
for k in 1..32 {
z ^= table[k][((x >> (124 - 4 * k)) & 0xF) as usize];
}
z
}
struct GhashTables {
tables: Vec<[[u128; 16]; 32]>,
powers: [u128; GHASH_GROUP],
}
impl GhashTables {
fn build(h: u128) -> Self {
let mut powers = [0u128; GHASH_GROUP];
powers[0] = h;
for i in 1..GHASH_GROUP {
powers[i] = gf128_mul(powers[i - 1], h);
}
let tables = powers.iter().map(|&g| build_gf128_table(g)).collect();
Self { tables, powers }
}
fn zeroize(&mut self) {
for t in &mut self.tables {
for row in t.iter_mut() {
row.fill(0);
}
}
self.powers.fill(0);
}
fn ghash_grouped(&self, it: &mut dyn Iterator<Item = u128>) -> u128 {
let mut y = 0u128;
let mut started = false;
loop {
let mut buf = [0u128; GHASH_GROUP];
let mut s = 0usize;
while s < GHASH_GROUP {
match it.next() {
Some(x) => {
buf[s] = x;
s += 1;
}
None => break,
}
}
if s == 0 {
return y;
}
let mut z = 0u128;
for (idx, &x) in buf.iter().enumerate().take(s) {
z ^= tbl_mul(&self.tables[s - 1 - idx], x);
}
if started {
y = gf128_mul(y, self.powers[s - 1]);
}
y ^= z;
started = true;
}
}
}
gcm_impl!(
Aes128Gcm,
Aes128GcmInner,
SoftAes128Gcm,
Aes128,
aes128_gcm,
16,
"AES-128-GCM AEAD 实例(密钥 Drop 时零化)。"
);
gcm_impl!(
Aes256Gcm,
Aes256GcmInner,
SoftAes256Gcm,
Aes256,
aes256_gcm,
32,
"AES-256-GCM AEAD 实例(密钥 Drop 时零化)。"
);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn gf128_properties() {
let a = u128::from_be_bytes([
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e,
0x0f, 0x10,
]);
let one = 1u128 << 127;
assert_eq!(gf128_mul(a, one), a, "X·1 = X");
assert_eq!(gf128_mul(one, a), a, "1·Y = Y");
assert_eq!(gf128_mul(0, a), 0);
let b = 0xdeadbeefcafef00d1234567890abcdefu128;
assert_eq!(gf128_mul(a, b), gf128_mul(b, a));
}
#[test]
fn tbl_mul_matches_bitwise() {
let mut x = 0x1234_5678_9abc_def0_0fed_cba9_8765_4321u128;
let g = 0xdead_beef_cafe_babe_0123_4567_89ab_cdefu128;
let table = build_gf128_table(g);
assert_eq!(tbl_mul(&table, x), gf128_mul(x, g));
assert_eq!(tbl_mul(&table, g), gf128_mul(g, g));
assert_eq!(tbl_mul(&table, 0), 0);
let one = 1u128 << 127;
assert_eq!(tbl_mul(&build_gf128_table(x), one), x);
for _ in 0..200 {
x ^= x << 13;
x ^= x >> 7;
x ^= x << 17;
assert_eq!(tbl_mul(&table, x), gf128_mul(x, g));
}
}
#[test]
fn grouped_ghash_matches_horner() {
let reference = |h: u128, aad: &[u8], ct: &[u8]| -> u128 {
let block = |c: &[u8]| {
let mut b = [0u8; 16];
b[..c.len()].copy_from_slice(c);
u128::from_be_bytes(b)
};
let mut y = 0u128;
for x in aad.chunks(16).map(&block).chain(ct.chunks(16).map(&block)) {
y = gf128_mul(y ^ x, h);
}
let mut len_b = [0u8; 16];
len_b[..8].copy_from_slice(&((aad.len() as u64) * 8).to_be_bytes());
len_b[8..].copy_from_slice(&((ct.len() as u64) * 8).to_be_bytes());
gf128_mul(y ^ u128::from_be_bytes(len_b), h)
};
let mut seed = 0x9E37_79B9u32;
let mut data = vec![0u8; 16 * 210 + 64];
for b in data.iter_mut() {
seed ^= seed << 13;
seed ^= seed >> 17;
seed ^= seed << 5;
*b = seed as u8;
}
let soft = SoftAes128Gcm::new(&[0x42u8; 16]);
let h = soft.h;
for aad_len in [0usize, 5, 31] {
for ct_len in [
0usize, 1, 15, 16, 17, 95, 96, 97, 111, 112, 113, 975, 976, 977, 991, 3199, 3215,
] {
let aad = &data[..aad_len];
let ct = &data[100..100 + ct_len];
assert_eq!(
soft.ghash(aad, ct),
reference(h, aad, ct),
"aad={aad_len} ct={ct_len}"
);
}
}
}
#[test]
fn seal_open_roundtrip() {
let g = Aes256Gcm::new(&[0x42; 32]);
let nonce = [0x11; 12];
let sealed = g.seal(&nonce, b"aad", b"plaintext, longer than one block ~~");
let opened = g.open(&nonce, b"aad", &sealed).expect("roundtrip");
assert_eq!(opened, b"plaintext, longer than one block ~~");
let mut bad = sealed.clone();
bad[0] ^= 1;
assert!(g.open(&nonce, b"aad", &bad).is_err());
let last = bad.len() - 1;
bad[last] ^= 1;
assert!(g.open(&nonce, b"aad", &bad).is_err());
}
}