Something went wrong. Try again.
we (web engine): Experimental web browser project to understand the limits of Claude
Something went wrong. Try again.
Rust
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857//! 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>, [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<Vec<u8>> { // 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>, [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<Vec<u8>> { 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>, [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<Vec<u8>> { 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<u8> { (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); }}