//! AES-128-GCM and AES-256-GCM authenticated encryption (NIST SP 800-38D). //! //! - AES block cipher (FIPS 197) with 128-bit and 256-bit keys //! - GHASH: Galois field multiplication in GF(2^128) //! - GCM encrypt/decrypt with 96-bit nonce and 128-bit authentication tag // --------------------------------------------------------------------------- // AES S-box (FIPS 197, §5.1.1) // --------------------------------------------------------------------------- const SBOX: [u8; 256] = [ 0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76, 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0, 0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15, 0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75, 0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84, 0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf, 0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8, 0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2, 0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73, 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb, 0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79, 0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08, 0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a, 0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e, 0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf, 0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16, ]; /// Round constants: x^(i-1) in GF(2^8) for i = 1..10. const RCON: [u8; 10] = [0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36]; // --------------------------------------------------------------------------- // AES key helpers // --------------------------------------------------------------------------- fn rot_word(w: u32) -> u32 { w.rotate_left(8) } fn sub_word(w: u32) -> u32 { let b = w.to_be_bytes(); u32::from_be_bytes([ SBOX[b[0] as usize], SBOX[b[1] as usize], SBOX[b[2] as usize], SBOX[b[3] as usize], ]) } // --------------------------------------------------------------------------- // AES key expansion (FIPS 197, §5.2) // --------------------------------------------------------------------------- fn aes128_expand_key(key: &[u8; 16]) -> Vec<[u8; 16]> { let mut w = [0u32; 44]; for i in 0..4 { w[i] = u32::from_be_bytes(key[4 * i..4 * i + 4].try_into().unwrap()); } for i in 4..44 { let mut temp = w[i - 1]; if i % 4 == 0 { temp = sub_word(rot_word(temp)) ^ ((RCON[i / 4 - 1] as u32) << 24); } w[i] = w[i - 4] ^ temp; } (0..11) .map(|r| { let mut rk = [0u8; 16]; for j in 0..4 { rk[4 * j..4 * j + 4].copy_from_slice(&w[4 * r + j].to_be_bytes()); } rk }) .collect() } fn aes256_expand_key(key: &[u8; 32]) -> Vec<[u8; 16]> { let mut w = [0u32; 60]; for i in 0..8 { w[i] = u32::from_be_bytes(key[4 * i..4 * i + 4].try_into().unwrap()); } for i in 8..60 { let mut temp = w[i - 1]; if i % 8 == 0 { temp = sub_word(rot_word(temp)) ^ ((RCON[i / 8 - 1] as u32) << 24); } else if i % 8 == 4 { temp = sub_word(temp); } w[i] = w[i - 8] ^ temp; } (0..15) .map(|r| { let mut rk = [0u8; 16]; for j in 0..4 { rk[4 * j..4 * j + 4].copy_from_slice(&w[4 * r + j].to_be_bytes()); } rk }) .collect() } // --------------------------------------------------------------------------- // AES round operations (FIPS 197, §5.1) // --------------------------------------------------------------------------- /// Multiply by x in GF(2^8) with irreducible polynomial x^8+x^4+x^3+x+1. fn xtime(a: u8) -> u8 { let shifted = (a as u16) << 1; let reduce = if a & 0x80 != 0 { 0x1b } else { 0x00 }; (shifted as u8) ^ reduce } fn sub_bytes(state: &mut [u8; 16]) { for b in state.iter_mut() { *b = SBOX[*b as usize]; } } /// ShiftRows: cyclic left-shift of row r by r positions. /// State layout: state[row + 4*col] (column-major, per FIPS 197). fn shift_rows(state: &mut [u8; 16]) { // Row 1: rotate left by 1 let t = state[1]; state[1] = state[5]; state[5] = state[9]; state[9] = state[13]; state[13] = t; // Row 2: rotate left by 2 let (t0, t1) = (state[2], state[6]); state[2] = state[10]; state[6] = state[14]; state[10] = t0; state[14] = t1; // Row 3: rotate left by 3 (= right by 1) let t = state[15]; state[15] = state[11]; state[11] = state[7]; state[7] = state[3]; state[3] = t; } /// MixColumns: matrix multiply each column in GF(2^8). fn mix_columns(state: &mut [u8; 16]) { for c in 0..4 { let i = 4 * c; let (s0, s1, s2, s3) = (state[i], state[i + 1], state[i + 2], state[i + 3]); state[i] = xtime(s0) ^ (xtime(s1) ^ s1) ^ s2 ^ s3; state[i + 1] = s0 ^ xtime(s1) ^ (xtime(s2) ^ s2) ^ s3; state[i + 2] = s0 ^ s1 ^ xtime(s2) ^ (xtime(s3) ^ s3); state[i + 3] = (xtime(s0) ^ s0) ^ s1 ^ s2 ^ xtime(s3); } } fn add_round_key(state: &mut [u8; 16], rk: &[u8; 16]) { for i in 0..16 { state[i] ^= rk[i]; } } // --------------------------------------------------------------------------- // AES block encrypt // --------------------------------------------------------------------------- fn aes_encrypt_block(block: &[u8; 16], round_keys: &[[u8; 16]]) -> [u8; 16] { let nr = round_keys.len() - 1; // 10 for AES-128, 14 for AES-256 let mut state = *block; add_round_key(&mut state, &round_keys[0]); for rk in round_keys.iter().take(nr).skip(1) { sub_bytes(&mut state); shift_rows(&mut state); mix_columns(&mut state); add_round_key(&mut state, rk); } // Final round (no MixColumns) sub_bytes(&mut state); shift_rows(&mut state); add_round_key(&mut state, &round_keys[nr]); state } // --------------------------------------------------------------------------- // GHASH: GF(2^128) multiplication (NIST SP 800-38D, §6.3) // --------------------------------------------------------------------------- /// Constant-time multiplication in GF(2^128). /// /// Uses the irreducible polynomial P = x^128 + x^7 + x^2 + x + 1. /// Bit ordering follows GCM convention: MSB of byte 0 = coefficient of x^0. fn gf128_mul(x: &[u8; 16], y: &[u8; 16]) -> [u8; 16] { let mut z_hi: u64 = 0; let mut z_lo: u64 = 0; let mut v_hi = u64::from_be_bytes(x[0..8].try_into().unwrap()); let mut v_lo = u64::from_be_bytes(x[8..16].try_into().unwrap()); for i in 0..128 { // y_bit = Y_i (coefficient of x^i in GCM convention) let y_bit = ((y[i / 8] >> (7 - (i % 8))) & 1) as u64; let mask = 0u64.wrapping_sub(y_bit); z_hi ^= v_hi & mask; z_lo ^= v_lo & mask; // Shift V right by 1 (toward higher powers); reduce if carry let carry = v_lo & 1; let carry_mask = 0u64.wrapping_sub(carry); v_lo = (v_lo >> 1) | ((v_hi & 1) << 63); v_hi = (v_hi >> 1) ^ (0xe100000000000000u64 & carry_mask); } let mut result = [0u8; 16]; result[0..8].copy_from_slice(&z_hi.to_be_bytes()); result[8..16].copy_from_slice(&z_lo.to_be_bytes()); result } fn xor_block(a: &mut [u8; 16], b: &[u8; 16]) { for i in 0..16 { a[i] ^= b[i]; } } /// Feed data blocks (with zero-padding of the final partial block) into GHASH. fn ghash_update(y: &mut [u8; 16], h: &[u8; 16], data: &[u8]) { let mut offset = 0; while offset + 16 <= data.len() { let block: [u8; 16] = data[offset..offset + 16].try_into().unwrap(); xor_block(y, &block); *y = gf128_mul(y, h); offset += 16; } if offset < data.len() { let mut block = [0u8; 16]; block[..data.len() - offset].copy_from_slice(&data[offset..]); xor_block(y, &block); *y = gf128_mul(y, h); } } /// GHASH(H, AAD, C) = process AAD ∥ C ∥ lengths through the universal hash. fn ghash(h: &[u8; 16], aad: &[u8], ciphertext: &[u8]) -> [u8; 16] { let mut y = [0u8; 16]; ghash_update(&mut y, h, aad); ghash_update(&mut y, h, ciphertext); // Length block: [len(A) in bits]_64 ∥ [len(C) in bits]_64 let mut len_block = [0u8; 16]; len_block[0..8].copy_from_slice(&((aad.len() as u64) * 8).to_be_bytes()); len_block[8..16].copy_from_slice(&((ciphertext.len() as u64) * 8).to_be_bytes()); xor_block(&mut y, &len_block); y = gf128_mul(&y, h); y } // --------------------------------------------------------------------------- // GCM counter // --------------------------------------------------------------------------- /// Increment the rightmost 32 bits of a 128-bit counter block. fn inc32(counter: &mut [u8; 16]) { let c = u32::from_be_bytes(counter[12..16].try_into().unwrap()); counter[12..16].copy_from_slice(&c.wrapping_add(1).to_be_bytes()); } // --------------------------------------------------------------------------- // Constant-time tag comparison // --------------------------------------------------------------------------- fn ct_eq_16(a: &[u8; 16], b: &[u8; 16]) -> bool { let mut diff = 0u8; for i in 0..16 { diff |= a[i] ^ b[i]; } diff == 0 } // --------------------------------------------------------------------------- // GCM core encrypt / decrypt // --------------------------------------------------------------------------- fn gcm_encrypt( round_keys: &[[u8; 16]], nonce: &[u8; 12], plaintext: &[u8], aad: &[u8], ) -> (Vec, [u8; 16]) { // H = E_K(0^128) let h = aes_encrypt_block(&[0u8; 16], round_keys); // J_0 = IV ∥ 0^31 ∥ 1 let mut j0 = [0u8; 16]; j0[0..12].copy_from_slice(nonce); j0[15] = 1; // GCTR: encrypt plaintext with counter starting at inc32(J_0) let mut counter = j0; inc32(&mut counter); let mut ciphertext = Vec::with_capacity(plaintext.len()); let mut offset = 0; while offset < plaintext.len() { let keystream = aes_encrypt_block(&counter, round_keys); let remaining = (plaintext.len() - offset).min(16); for i in 0..remaining { ciphertext.push(plaintext[offset + i] ^ keystream[i]); } offset += remaining; inc32(&mut counter); } // Tag = GHASH(H, AAD, C) ⊕ E_K(J_0) let s = ghash(&h, aad, &ciphertext); let ek_j0 = aes_encrypt_block(&j0, round_keys); let mut tag = [0u8; 16]; for i in 0..16 { tag[i] = s[i] ^ ek_j0[i]; } (ciphertext, tag) } fn gcm_decrypt( round_keys: &[[u8; 16]], nonce: &[u8; 12], ciphertext: &[u8], aad: &[u8], tag: &[u8; 16], ) -> Option> { // H = E_K(0^128) let h = aes_encrypt_block(&[0u8; 16], round_keys); // J_0 = IV ∥ 0^31 ∥ 1 let mut j0 = [0u8; 16]; j0[0..12].copy_from_slice(nonce); j0[15] = 1; // Verify tag before decrypting let s = ghash(&h, aad, ciphertext); let ek_j0 = aes_encrypt_block(&j0, round_keys); let mut expected_tag = [0u8; 16]; for i in 0..16 { expected_tag[i] = s[i] ^ ek_j0[i]; } if !ct_eq_16(&expected_tag, tag) { return None; } // GCTR: decrypt ciphertext let mut counter = j0; inc32(&mut counter); let mut plaintext = Vec::with_capacity(ciphertext.len()); let mut offset = 0; while offset < ciphertext.len() { let keystream = aes_encrypt_block(&counter, round_keys); let remaining = (ciphertext.len() - offset).min(16); for i in 0..remaining { plaintext.push(ciphertext[offset + i] ^ keystream[i]); } offset += remaining; inc32(&mut counter); } Some(plaintext) } // --------------------------------------------------------------------------- // Public API // --------------------------------------------------------------------------- /// AES-128-GCM encrypt. Returns `(ciphertext, 128-bit tag)`. pub fn aes128_gcm_encrypt( key: &[u8; 16], nonce: &[u8; 12], plaintext: &[u8], aad: &[u8], ) -> (Vec, [u8; 16]) { let rk = aes128_expand_key(key); gcm_encrypt(&rk, nonce, plaintext, aad) } /// AES-128-GCM decrypt. Returns `None` if the authentication tag is invalid. pub fn aes128_gcm_decrypt( key: &[u8; 16], nonce: &[u8; 12], ciphertext: &[u8], aad: &[u8], tag: &[u8; 16], ) -> Option> { let rk = aes128_expand_key(key); gcm_decrypt(&rk, nonce, ciphertext, aad, tag) } /// AES-256-GCM encrypt. Returns `(ciphertext, 128-bit tag)`. pub fn aes256_gcm_encrypt( key: &[u8; 32], nonce: &[u8; 12], plaintext: &[u8], aad: &[u8], ) -> (Vec, [u8; 16]) { let rk = aes256_expand_key(key); gcm_encrypt(&rk, nonce, plaintext, aad) } /// AES-256-GCM decrypt. Returns `None` if the authentication tag is invalid. pub fn aes256_gcm_decrypt( key: &[u8; 32], nonce: &[u8; 12], ciphertext: &[u8], aad: &[u8], tag: &[u8; 16], ) -> Option> { let rk = aes256_expand_key(key); gcm_decrypt(&rk, nonce, ciphertext, aad, tag) } // --------------------------------------------------------------------------- // Tests // --------------------------------------------------------------------------- #[cfg(test)] mod tests { use super::*; fn hex(bytes: &[u8]) -> String { bytes.iter().map(|b| format!("{b:02x}")).collect() } fn from_hex(s: &str) -> Vec { (0..s.len()) .step_by(2) .map(|i| u8::from_str_radix(&s[i..i + 2], 16).unwrap()) .collect() } fn hex16(s: &str) -> [u8; 16] { from_hex(s).try_into().unwrap() } // ----------------------------------------------------------------------- // AES block cipher (ECB) — NIST test vectors // ----------------------------------------------------------------------- #[test] fn aes128_ecb_zero_key() { let key = [0u8; 16]; let pt = [0u8; 16]; let rk = aes128_expand_key(&key); assert_eq!( hex(&aes_encrypt_block(&pt, &rk)), "66e94bd4ef8a2c3b884cfa59ca342b2e" ); } #[test] fn aes128_ecb_nist() { let key = hex16("2b7e151628aed2a6abf7158809cf4f3c"); let pt = hex16("6bc1bee22e409f96e93d7e117393172a"); let rk = aes128_expand_key(&key); assert_eq!( hex(&aes_encrypt_block(&pt, &rk)), "3ad77bb40d7a3660a89ecaf32466ef97" ); } #[test] fn aes256_ecb_zero_key() { let key = [0u8; 32]; let pt = [0u8; 16]; let rk = aes256_expand_key(&key); assert_eq!( hex(&aes_encrypt_block(&pt, &rk)), "dc95c078a2408989ad48a21492842087" ); } #[test] fn aes256_ecb_nist() { let key: [u8; 32] = from_hex("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4") .try_into() .unwrap(); let pt = hex16("6bc1bee22e409f96e93d7e117393172a"); let rk = aes256_expand_key(&key); assert_eq!( hex(&aes_encrypt_block(&pt, &rk)), "f3eed1bdb5d2a03c064b5a7e3db181f8" ); } // ----------------------------------------------------------------------- // GCM Test Case 1: AES-128, empty plaintext, empty AAD // ----------------------------------------------------------------------- #[test] fn gcm_128_case1_encrypt() { let key = [0u8; 16]; let nonce = [0u8; 12]; let (ct, tag) = aes128_gcm_encrypt(&key, &nonce, &[], &[]); assert!(ct.is_empty()); assert_eq!(hex(&tag), "58e2fccefa7e3061367f1d57a4e7455a"); } #[test] fn gcm_128_case1_decrypt() { let key = [0u8; 16]; let nonce = [0u8; 12]; let tag = hex16("58e2fccefa7e3061367f1d57a4e7455a"); let pt = aes128_gcm_decrypt(&key, &nonce, &[], &[], &tag).unwrap(); assert!(pt.is_empty()); } // ----------------------------------------------------------------------- // GCM Test Case 2: AES-128, 16-byte plaintext, empty AAD // ----------------------------------------------------------------------- #[test] fn gcm_128_case2_encrypt() { let key = [0u8; 16]; let nonce = [0u8; 12]; let pt = [0u8; 16]; let (ct, tag) = aes128_gcm_encrypt(&key, &nonce, &pt, &[]); assert_eq!(hex(&ct), "0388dace60b6a392f328c2b971b2fe78"); assert_eq!(hex(&tag), "ab6e47d42cec13bdf53a67b21257bddf"); } #[test] fn gcm_128_case2_decrypt() { let key = [0u8; 16]; let nonce = [0u8; 12]; let ct = from_hex("0388dace60b6a392f328c2b971b2fe78"); let tag = hex16("ab6e47d42cec13bdf53a67b21257bddf"); let pt = aes128_gcm_decrypt(&key, &nonce, &ct, &[], &tag).unwrap(); assert_eq!(pt, vec![0u8; 16]); } // ----------------------------------------------------------------------- // GCM Test Case 3: AES-128, 64-byte plaintext, empty AAD // ----------------------------------------------------------------------- #[test] fn gcm_128_case3_encrypt() { let key = hex16("feffe9928665731c6d6a8f9467308308"); let nonce: [u8; 12] = from_hex("cafebabefacedbaddecaf888").try_into().unwrap(); let pt = from_hex( "d9313225f88406e5a55909c5aff5269a\ 86a7a9531534f7da2e4c303d8a318a72\ 1c3c0c95956809532fcf0e2449a6b525\ b16aedf5aa0de657ba637b391aafd255", ); let (ct, tag) = aes128_gcm_encrypt(&key, &nonce, &pt, &[]); assert_eq!( hex(&ct), "42831ec2217774244b7221b784d0d49c\ e3aa212f2c02a4e035c17e2329aca12e\ 21d514b25466931c7d8f6a5aac84aa05\ 1ba30b396a0aac973d58e091473f5985" ); assert_eq!(hex(&tag), "4d5c2af327cd64a62cf35abd2ba6fab4"); } #[test] fn gcm_128_case3_decrypt() { let key = hex16("feffe9928665731c6d6a8f9467308308"); let nonce: [u8; 12] = from_hex("cafebabefacedbaddecaf888").try_into().unwrap(); let ct = from_hex( "42831ec2217774244b7221b784d0d49c\ e3aa212f2c02a4e035c17e2329aca12e\ 21d514b25466931c7d8f6a5aac84aa05\ 1ba30b396a0aac973d58e091473f5985", ); let tag = hex16("4d5c2af327cd64a62cf35abd2ba6fab4"); let pt = aes128_gcm_decrypt(&key, &nonce, &ct, &[], &tag).unwrap(); assert_eq!( hex(&pt), "d9313225f88406e5a55909c5aff5269a\ 86a7a9531534f7da2e4c303d8a318a72\ 1c3c0c95956809532fcf0e2449a6b525\ b16aedf5aa0de657ba637b391aafd255" ); } // ----------------------------------------------------------------------- // GCM Test Case 4: AES-128, 60-byte plaintext, 20-byte AAD // ----------------------------------------------------------------------- #[test] fn gcm_128_case4_encrypt() { let key = hex16("feffe9928665731c6d6a8f9467308308"); let nonce: [u8; 12] = from_hex("cafebabefacedbaddecaf888").try_into().unwrap(); let pt = from_hex( "d9313225f88406e5a55909c5aff5269a\ 86a7a9531534f7da2e4c303d8a318a72\ 1c3c0c95956809532fcf0e2449a6b525\ b16aedf5aa0de657ba637b39", ); let aad = from_hex("feedfacedeadbeeffeedfacedeadbeefabaddad2"); let (ct, tag) = aes128_gcm_encrypt(&key, &nonce, &pt, &aad); assert_eq!( hex(&ct), "42831ec2217774244b7221b784d0d49c\ e3aa212f2c02a4e035c17e2329aca12e\ 21d514b25466931c7d8f6a5aac84aa05\ 1ba30b396a0aac973d58e091", ); assert_eq!(hex(&tag), "5bc94fbc3221a5db94fae95ae7121a47"); } #[test] fn gcm_128_case4_decrypt() { let key = hex16("feffe9928665731c6d6a8f9467308308"); let nonce: [u8; 12] = from_hex("cafebabefacedbaddecaf888").try_into().unwrap(); let ct = from_hex( "42831ec2217774244b7221b784d0d49c\ e3aa212f2c02a4e035c17e2329aca12e\ 21d514b25466931c7d8f6a5aac84aa05\ 1ba30b396a0aac973d58e091", ); let aad = from_hex("feedfacedeadbeeffeedfacedeadbeefabaddad2"); let tag = hex16("5bc94fbc3221a5db94fae95ae7121a47"); let pt = aes128_gcm_decrypt(&key, &nonce, &ct, &aad, &tag).unwrap(); assert_eq!( hex(&pt), "d9313225f88406e5a55909c5aff5269a\ 86a7a9531534f7da2e4c303d8a318a72\ 1c3c0c95956809532fcf0e2449a6b525\ b16aedf5aa0de657ba637b39" ); } // ----------------------------------------------------------------------- // GCM Test Case 13: AES-256, empty plaintext, empty AAD // ----------------------------------------------------------------------- #[test] fn gcm_256_case13_encrypt() { let key = [0u8; 32]; let nonce = [0u8; 12]; let (ct, tag) = aes256_gcm_encrypt(&key, &nonce, &[], &[]); assert!(ct.is_empty()); assert_eq!(hex(&tag), "530f8afbc74536b9a963b4f1c4cb738b"); } #[test] fn gcm_256_case13_decrypt() { let key = [0u8; 32]; let nonce = [0u8; 12]; let tag = hex16("530f8afbc74536b9a963b4f1c4cb738b"); let pt = aes256_gcm_decrypt(&key, &nonce, &[], &[], &tag).unwrap(); assert!(pt.is_empty()); } // ----------------------------------------------------------------------- // GCM Test Case 14: AES-256, 16-byte plaintext, empty AAD // ----------------------------------------------------------------------- #[test] fn gcm_256_case14_encrypt() { let key = [0u8; 32]; let nonce = [0u8; 12]; let pt = [0u8; 16]; let (ct, tag) = aes256_gcm_encrypt(&key, &nonce, &pt, &[]); assert_eq!(hex(&ct), "cea7403d4d606b6e074ec5d3baf39d18"); assert_eq!(hex(&tag), "d0d1c8a799996bf0265b98b5d48ab919"); } #[test] fn gcm_256_case14_decrypt() { let key = [0u8; 32]; let nonce = [0u8; 12]; let ct = from_hex("cea7403d4d606b6e074ec5d3baf39d18"); let tag = hex16("d0d1c8a799996bf0265b98b5d48ab919"); let pt = aes256_gcm_decrypt(&key, &nonce, &ct, &[], &tag).unwrap(); assert_eq!(pt, vec![0u8; 16]); } // ----------------------------------------------------------------------- // GCM Test Case 15: AES-256, 64-byte plaintext, empty AAD // ----------------------------------------------------------------------- #[test] fn gcm_256_case15_encrypt() { let key: [u8; 32] = from_hex("feffe9928665731c6d6a8f9467308308feffe9928665731c6d6a8f9467308308") .try_into() .unwrap(); let nonce: [u8; 12] = from_hex("cafebabefacedbaddecaf888").try_into().unwrap(); let pt = from_hex( "d9313225f88406e5a55909c5aff5269a\ 86a7a9531534f7da2e4c303d8a318a72\ 1c3c0c95956809532fcf0e2449a6b525\ b16aedf5aa0de657ba637b391aafd255", ); let (ct, tag) = aes256_gcm_encrypt(&key, &nonce, &pt, &[]); assert_eq!( hex(&ct), "522dc1f099567d07f47f37a32a84427d\ 643a8cdcbfe5c0c97598a2bd2555d1aa\ 8cb08e48590dbb3da7b08b1056828838\ c5f61e6393ba7a0abcc9f662898015ad" ); assert_eq!(hex(&tag), "b094dac5d93471bdec1a502270e3cc6c"); } #[test] fn gcm_256_case15_decrypt() { let key: [u8; 32] = from_hex("feffe9928665731c6d6a8f9467308308feffe9928665731c6d6a8f9467308308") .try_into() .unwrap(); let nonce: [u8; 12] = from_hex("cafebabefacedbaddecaf888").try_into().unwrap(); let ct = from_hex( "522dc1f099567d07f47f37a32a84427d\ 643a8cdcbfe5c0c97598a2bd2555d1aa\ 8cb08e48590dbb3da7b08b1056828838\ c5f61e6393ba7a0abcc9f662898015ad", ); let tag = hex16("b094dac5d93471bdec1a502270e3cc6c"); let pt = aes256_gcm_decrypt(&key, &nonce, &ct, &[], &tag).unwrap(); assert_eq!( hex(&pt), "d9313225f88406e5a55909c5aff5269a\ 86a7a9531534f7da2e4c303d8a318a72\ 1c3c0c95956809532fcf0e2449a6b525\ b16aedf5aa0de657ba637b391aafd255" ); } // ----------------------------------------------------------------------- // GCM Test Case 16: AES-256, 60-byte plaintext, 20-byte AAD // ----------------------------------------------------------------------- #[test] fn gcm_256_case16_encrypt() { let key: [u8; 32] = from_hex("feffe9928665731c6d6a8f9467308308feffe9928665731c6d6a8f9467308308") .try_into() .unwrap(); let nonce: [u8; 12] = from_hex("cafebabefacedbaddecaf888").try_into().unwrap(); let pt = from_hex( "d9313225f88406e5a55909c5aff5269a\ 86a7a9531534f7da2e4c303d8a318a72\ 1c3c0c95956809532fcf0e2449a6b525\ b16aedf5aa0de657ba637b39", ); let aad = from_hex("feedfacedeadbeeffeedfacedeadbeefabaddad2"); let (ct, tag) = aes256_gcm_encrypt(&key, &nonce, &pt, &aad); assert_eq!( hex(&ct), "522dc1f099567d07f47f37a32a84427d\ 643a8cdcbfe5c0c97598a2bd2555d1aa\ 8cb08e48590dbb3da7b08b1056828838\ c5f61e6393ba7a0abcc9f662", ); assert_eq!(hex(&tag), "76fc6ece0f4e1768cddf8853bb2d551b"); } #[test] fn gcm_256_case16_decrypt() { let key: [u8; 32] = from_hex("feffe9928665731c6d6a8f9467308308feffe9928665731c6d6a8f9467308308") .try_into() .unwrap(); let nonce: [u8; 12] = from_hex("cafebabefacedbaddecaf888").try_into().unwrap(); let ct = from_hex( "522dc1f099567d07f47f37a32a84427d\ 643a8cdcbfe5c0c97598a2bd2555d1aa\ 8cb08e48590dbb3da7b08b1056828838\ c5f61e6393ba7a0abcc9f662", ); let aad = from_hex("feedfacedeadbeeffeedfacedeadbeefabaddad2"); let tag = hex16("76fc6ece0f4e1768cddf8853bb2d551b"); let pt = aes256_gcm_decrypt(&key, &nonce, &ct, &aad, &tag).unwrap(); assert_eq!( hex(&pt), "d9313225f88406e5a55909c5aff5269a\ 86a7a9531534f7da2e4c303d8a318a72\ 1c3c0c95956809532fcf0e2449a6b525\ b16aedf5aa0de657ba637b39" ); } // ----------------------------------------------------------------------- // Tag tamper detection // ----------------------------------------------------------------------- #[test] fn decrypt_rejects_tampered_tag() { let key = [0u8; 16]; let nonce = [0u8; 12]; let pt = b"hello world"; let (ct, mut tag) = aes128_gcm_encrypt(&key, &nonce, pt, &[]); tag[0] ^= 1; assert!(aes128_gcm_decrypt(&key, &nonce, &ct, &[], &tag).is_none()); } #[test] fn decrypt_rejects_tampered_ciphertext() { let key = [0u8; 16]; let nonce = [0u8; 12]; let pt = b"hello world"; let (mut ct, tag) = aes128_gcm_encrypt(&key, &nonce, pt, &[]); ct[0] ^= 1; assert!(aes128_gcm_decrypt(&key, &nonce, &ct, &[], &tag).is_none()); } #[test] fn decrypt_rejects_tampered_aad() { let key = [0u8; 16]; let nonce = [0u8; 12]; let pt = b"hello world"; let aad = b"associated data"; let (ct, tag) = aes128_gcm_encrypt(&key, &nonce, pt, aad); let bad_aad = b"Associated data"; assert!(aes128_gcm_decrypt(&key, &nonce, &ct, bad_aad, &tag).is_none()); } // ----------------------------------------------------------------------- // Round-trip // ----------------------------------------------------------------------- #[test] fn aes128_gcm_roundtrip() { let key = hex16("deadbeefdeadbeefdeadbeefdeadbeef"); let nonce: [u8; 12] = from_hex("0102030405060708090a0b0c").try_into().unwrap(); let pt = b"The quick brown fox jumps over the lazy dog"; let aad = b"additional authenticated data"; let (ct, tag) = aes128_gcm_encrypt(&key, &nonce, pt, aad); let recovered = aes128_gcm_decrypt(&key, &nonce, &ct, aad, &tag).unwrap(); assert_eq!(recovered, pt); } #[test] fn aes256_gcm_roundtrip() { let key: [u8; 32] = from_hex("deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef") .try_into() .unwrap(); let nonce: [u8; 12] = from_hex("0102030405060708090a0b0c").try_into().unwrap(); let pt = b"The quick brown fox jumps over the lazy dog"; let aad = b"additional authenticated data"; let (ct, tag) = aes256_gcm_encrypt(&key, &nonce, pt, aad); let recovered = aes256_gcm_decrypt(&key, &nonce, &ct, aad, &tag).unwrap(); assert_eq!(recovered, pt); } }