use super::{BlockCipher, TagMismatch};
use crate::ct::ConstantTimeEq;
const R: u128 = 0xe1000000000000000000000000000000;
fn gf_mul(x: u128, y: u128) -> u128 {
let mut z = 0u128;
let mut v = y;
let mut i = 0;
while i < 128 {
let xi = (x >> (127 - i)) & 1;
z ^= 0u128.wrapping_sub(xi) & v;
let lsb = v & 1;
v >>= 1;
v ^= 0u128.wrapping_sub(lsb) & R;
i += 1;
}
z
}
#[inline]
fn load_block(chunk: &[u8]) -> u128 {
let mut b = [0u8; 16];
b[..chunk.len()].copy_from_slice(chunk);
u128::from_be_bytes(b)
}
#[inline]
fn inc32(block: u128) -> u128 {
let ctr = (block as u32).wrapping_add(1);
(block & !0xffff_ffffu128) | ctr as u128
}
#[derive(Clone)]
pub struct Gcm<C: BlockCipher> {
cipher: C,
h: u128,
}
impl<C: BlockCipher> Gcm<C> {
pub fn new(cipher: C) -> Self {
let mut h = [0u8; 16];
cipher.encrypt_block(&mut h);
Gcm {
cipher,
h: u128::from_be_bytes(h),
}
}
fn j0(&self, nonce: &[u8]) -> u128 {
if nonce.len() == 12 {
let mut block = [0u8; 16];
block[..12].copy_from_slice(nonce);
block[15] = 1;
u128::from_be_bytes(block)
} else {
let mut x = 0u128;
for chunk in nonce.chunks(16) {
x = gf_mul(x ^ load_block(chunk), self.h);
}
x = gf_mul(x ^ (nonce.len() as u128 * 8), self.h);
x
}
}
fn ghash(&self, aad: &[u8], ct: &[u8]) -> u128 {
let mut x = 0u128;
for chunk in aad.chunks(16) {
x = gf_mul(x ^ load_block(chunk), self.h);
}
for chunk in ct.chunks(16) {
x = gf_mul(x ^ load_block(chunk), self.h);
}
let len_block = ((aad.len() as u128 * 8) << 64) | (ct.len() as u128 * 8);
gf_mul(x ^ len_block, self.h)
}
fn gctr(&self, mut counter: u128, buf: &mut [u8]) {
let mut ks = [0u8; 16];
let mut pos = 16;
for byte in buf.iter_mut() {
if pos == 16 {
ks = counter.to_be_bytes();
self.cipher.encrypt_block(&mut ks);
counter = inc32(counter);
pos = 0;
}
*byte ^= ks[pos];
pos += 1;
}
}
fn tag(&self, j0: u128, aad: &[u8], ct: &[u8]) -> [u8; 16] {
let s = self.ghash(aad, ct);
let mut ej0 = j0.to_be_bytes();
self.cipher.encrypt_block(&mut ej0);
(u128::from_be_bytes(ej0) ^ s).to_be_bytes()
}
const MAX_NONCE_LEN: usize = (1usize << 61) - 1;
pub const MAX_PLAINTEXT_LEN: u64 = (1u64 << 36) - 32;
fn validate(nonce: &[u8], buffer: &[u8]) {
assert!(
!nonce.is_empty() && nonce.len() <= Self::MAX_NONCE_LEN,
"AES-GCM nonce must be 1..=2^61-1 bytes (NIST SP 800-38D §5.2.1.1)"
);
assert!(
(buffer.len() as u64) <= Self::MAX_PLAINTEXT_LEN,
"AES-GCM plaintext exceeds 2^39 − 256 bits (NIST SP 800-38D §5.2.1.1)"
);
}
pub fn encrypt(&self, nonce: &[u8], aad: &[u8], buffer: &mut [u8]) -> [u8; 16] {
Self::validate(nonce, buffer);
let j0 = self.j0(nonce);
self.gctr(inc32(j0), buffer);
self.tag(j0, aad, buffer)
}
pub fn decrypt(
&self,
nonce: &[u8],
aad: &[u8],
buffer: &mut [u8],
tag: &[u8; 16],
) -> Result<(), TagMismatch> {
Self::validate(nonce, buffer);
let j0 = self.j0(nonce);
let expected = self.tag(j0, aad, buffer);
if !bool::from(expected.ct_eq(tag)) {
return Err(TagMismatch);
}
self.gctr(inc32(j0), buffer);
Ok(())
}
}
impl<C: BlockCipher> Drop for Gcm<C> {
fn drop(&mut self) {
self.h = 0;
let _ = core::hint::black_box(&self.h);
}
}
pub type Aes128Gcm = Gcm<super::Aes128>;
pub type Aes256Gcm = Gcm<super::Aes256>;
#[cfg(test)]
mod tests {
use super::*;
use crate::cipher::Aes128;
use crate::test_util::from_hex;
fn gcm128(key_hex: &str) -> Gcm<Aes128> {
Gcm::new(Aes128::new(&from_hex::<16>(key_hex)))
}
#[test]
fn tc1_empty() {
let g = gcm128("00000000000000000000000000000000");
let nonce = from_hex::<12>("000000000000000000000000");
let mut buf: [u8; 0] = [];
let tag = g.encrypt(&nonce, &[], &mut buf);
assert_eq!(tag, from_hex::<16>("58e2fccefa7e3061367f1d57a4e7455a"));
}
#[test]
fn tc2_one_block() {
let g = gcm128("00000000000000000000000000000000");
let nonce = from_hex::<12>("000000000000000000000000");
let mut buf = from_hex::<16>("00000000000000000000000000000000");
let tag = g.encrypt(&nonce, &[], &mut buf);
assert_eq!(buf, from_hex::<16>("0388dace60b6a392f328c2b971b2fe78"));
assert_eq!(tag, from_hex::<16>("ab6e47d42cec13bdf53a67b21257bddf"));
}
#[test]
fn tc3_multiblock() {
let g = gcm128("feffe9928665731c6d6a8f9467308308");
let nonce = from_hex::<12>("cafebabefacedbaddecaf888");
let mut buf = from_hex::<64>(
"d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a72\
1c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b391aafd255",
);
let tag = g.encrypt(&nonce, &[], &mut buf);
assert_eq!(
buf,
from_hex::<64>(
"42831ec2217774244b7221b784d0d49ce3aa212f2c02a4e035c17e2329aca12e\
21d514b25466931c7d8f6a5aac84aa051ba30b396a0aac973d58e091473f5985"
)
);
assert_eq!(tag, from_hex::<16>("4d5c2af327cd64a62cf35abd2ba6fab4"));
}
#[test]
fn tc4_with_aad() {
let g = gcm128("feffe9928665731c6d6a8f9467308308");
let nonce = from_hex::<12>("cafebabefacedbaddecaf888");
let aad = from_hex::<20>("feedfacedeadbeeffeedfacedeadbeefabaddad2");
let mut buf = from_hex::<60>(
"d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a72\
1c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b39",
);
let tag = g.encrypt(&nonce, &aad, &mut buf);
assert_eq!(
buf,
from_hex::<60>(
"42831ec2217774244b7221b784d0d49ce3aa212f2c02a4e035c17e2329aca12e\
21d514b25466931c7d8f6a5aac84aa051ba30b396a0aac973d58e091"
)
);
assert_eq!(tag, from_hex::<16>("5bc94fbc3221a5db94fae95ae7121a47"));
}
#[test]
fn decrypt_roundtrip_and_reject() {
let g = gcm128("feffe9928665731c6d6a8f9467308308");
let nonce = from_hex::<12>("cafebabefacedbaddecaf888");
let aad = from_hex::<20>("feedfacedeadbeeffeedfacedeadbeefabaddad2");
let plaintext = from_hex::<60>(
"d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a72\
1c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b39",
);
let mut buf = plaintext;
let tag = g.encrypt(&nonce, &aad, &mut buf);
g.decrypt(&nonce, &aad, &mut buf, &tag).unwrap();
assert_eq!(buf, plaintext);
let mut ct = plaintext;
let tag = g.encrypt(&nonce, &aad, &mut ct);
let ciphertext = ct;
let mut bad_tag = tag;
bad_tag[0] ^= 1;
assert_eq!(g.decrypt(&nonce, &aad, &mut ct, &bad_tag), Err(TagMismatch));
assert_eq!(ct, ciphertext, "buffer must be unchanged on auth failure");
let mut ct = ciphertext;
let mut bad_aad = aad;
bad_aad[0] ^= 1;
assert_eq!(g.decrypt(&nonce, &bad_aad, &mut ct, &tag), Err(TagMismatch));
}
}