we (web engine): Experimental web browser project to understand the limits of Claude
at css-loading 856 lines 30 kB view raw
1//! AES-128-GCM and AES-256-GCM authenticated encryption (NIST SP 800-38D). 2//! 3//! - AES block cipher (FIPS 197) with 128-bit and 256-bit keys 4//! - GHASH: Galois field multiplication in GF(2^128) 5//! - GCM encrypt/decrypt with 96-bit nonce and 128-bit authentication tag 6 7// --------------------------------------------------------------------------- 8// AES S-box (FIPS 197, §5.1.1) 9// --------------------------------------------------------------------------- 10 11const SBOX: [u8; 256] = [ 12 0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76, 13 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0, 14 0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15, 15 0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75, 16 0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84, 17 0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf, 18 0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8, 19 0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2, 20 0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73, 21 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb, 22 0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79, 23 0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08, 24 0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a, 25 0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e, 26 0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf, 27 0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16, 28]; 29 30/// Round constants: x^(i-1) in GF(2^8) for i = 1..10. 31const RCON: [u8; 10] = [0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36]; 32 33// --------------------------------------------------------------------------- 34// AES key helpers 35// --------------------------------------------------------------------------- 36 37fn rot_word(w: u32) -> u32 { 38 w.rotate_left(8) 39} 40 41fn sub_word(w: u32) -> u32 { 42 let b = w.to_be_bytes(); 43 u32::from_be_bytes([ 44 SBOX[b[0] as usize], 45 SBOX[b[1] as usize], 46 SBOX[b[2] as usize], 47 SBOX[b[3] as usize], 48 ]) 49} 50 51// --------------------------------------------------------------------------- 52// AES key expansion (FIPS 197, §5.2) 53// --------------------------------------------------------------------------- 54 55fn aes128_expand_key(key: &[u8; 16]) -> Vec<[u8; 16]> { 56 let mut w = [0u32; 44]; 57 for i in 0..4 { 58 w[i] = u32::from_be_bytes(key[4 * i..4 * i + 4].try_into().unwrap()); 59 } 60 for i in 4..44 { 61 let mut temp = w[i - 1]; 62 if i % 4 == 0 { 63 temp = sub_word(rot_word(temp)) ^ ((RCON[i / 4 - 1] as u32) << 24); 64 } 65 w[i] = w[i - 4] ^ temp; 66 } 67 (0..11) 68 .map(|r| { 69 let mut rk = [0u8; 16]; 70 for j in 0..4 { 71 rk[4 * j..4 * j + 4].copy_from_slice(&w[4 * r + j].to_be_bytes()); 72 } 73 rk 74 }) 75 .collect() 76} 77 78fn aes256_expand_key(key: &[u8; 32]) -> Vec<[u8; 16]> { 79 let mut w = [0u32; 60]; 80 for i in 0..8 { 81 w[i] = u32::from_be_bytes(key[4 * i..4 * i + 4].try_into().unwrap()); 82 } 83 for i in 8..60 { 84 let mut temp = w[i - 1]; 85 if i % 8 == 0 { 86 temp = sub_word(rot_word(temp)) ^ ((RCON[i / 8 - 1] as u32) << 24); 87 } else if i % 8 == 4 { 88 temp = sub_word(temp); 89 } 90 w[i] = w[i - 8] ^ temp; 91 } 92 (0..15) 93 .map(|r| { 94 let mut rk = [0u8; 16]; 95 for j in 0..4 { 96 rk[4 * j..4 * j + 4].copy_from_slice(&w[4 * r + j].to_be_bytes()); 97 } 98 rk 99 }) 100 .collect() 101} 102 103// --------------------------------------------------------------------------- 104// AES round operations (FIPS 197, §5.1) 105// --------------------------------------------------------------------------- 106 107/// Multiply by x in GF(2^8) with irreducible polynomial x^8+x^4+x^3+x+1. 108fn xtime(a: u8) -> u8 { 109 let shifted = (a as u16) << 1; 110 let reduce = if a & 0x80 != 0 { 0x1b } else { 0x00 }; 111 (shifted as u8) ^ reduce 112} 113 114fn sub_bytes(state: &mut [u8; 16]) { 115 for b in state.iter_mut() { 116 *b = SBOX[*b as usize]; 117 } 118} 119 120/// ShiftRows: cyclic left-shift of row r by r positions. 121/// State layout: state[row + 4*col] (column-major, per FIPS 197). 122fn shift_rows(state: &mut [u8; 16]) { 123 // Row 1: rotate left by 1 124 let t = state[1]; 125 state[1] = state[5]; 126 state[5] = state[9]; 127 state[9] = state[13]; 128 state[13] = t; 129 130 // Row 2: rotate left by 2 131 let (t0, t1) = (state[2], state[6]); 132 state[2] = state[10]; 133 state[6] = state[14]; 134 state[10] = t0; 135 state[14] = t1; 136 137 // Row 3: rotate left by 3 (= right by 1) 138 let t = state[15]; 139 state[15] = state[11]; 140 state[11] = state[7]; 141 state[7] = state[3]; 142 state[3] = t; 143} 144 145/// MixColumns: matrix multiply each column in GF(2^8). 146fn mix_columns(state: &mut [u8; 16]) { 147 for c in 0..4 { 148 let i = 4 * c; 149 let (s0, s1, s2, s3) = (state[i], state[i + 1], state[i + 2], state[i + 3]); 150 state[i] = xtime(s0) ^ (xtime(s1) ^ s1) ^ s2 ^ s3; 151 state[i + 1] = s0 ^ xtime(s1) ^ (xtime(s2) ^ s2) ^ s3; 152 state[i + 2] = s0 ^ s1 ^ xtime(s2) ^ (xtime(s3) ^ s3); 153 state[i + 3] = (xtime(s0) ^ s0) ^ s1 ^ s2 ^ xtime(s3); 154 } 155} 156 157fn add_round_key(state: &mut [u8; 16], rk: &[u8; 16]) { 158 for i in 0..16 { 159 state[i] ^= rk[i]; 160 } 161} 162 163// --------------------------------------------------------------------------- 164// AES block encrypt 165// --------------------------------------------------------------------------- 166 167fn aes_encrypt_block(block: &[u8; 16], round_keys: &[[u8; 16]]) -> [u8; 16] { 168 let nr = round_keys.len() - 1; // 10 for AES-128, 14 for AES-256 169 let mut state = *block; 170 171 add_round_key(&mut state, &round_keys[0]); 172 173 for rk in round_keys.iter().take(nr).skip(1) { 174 sub_bytes(&mut state); 175 shift_rows(&mut state); 176 mix_columns(&mut state); 177 add_round_key(&mut state, rk); 178 } 179 180 // Final round (no MixColumns) 181 sub_bytes(&mut state); 182 shift_rows(&mut state); 183 add_round_key(&mut state, &round_keys[nr]); 184 185 state 186} 187 188// --------------------------------------------------------------------------- 189// GHASH: GF(2^128) multiplication (NIST SP 800-38D, §6.3) 190// --------------------------------------------------------------------------- 191 192/// Constant-time multiplication in GF(2^128). 193/// 194/// Uses the irreducible polynomial P = x^128 + x^7 + x^2 + x + 1. 195/// Bit ordering follows GCM convention: MSB of byte 0 = coefficient of x^0. 196fn gf128_mul(x: &[u8; 16], y: &[u8; 16]) -> [u8; 16] { 197 let mut z_hi: u64 = 0; 198 let mut z_lo: u64 = 0; 199 200 let mut v_hi = u64::from_be_bytes(x[0..8].try_into().unwrap()); 201 let mut v_lo = u64::from_be_bytes(x[8..16].try_into().unwrap()); 202 203 for i in 0..128 { 204 // y_bit = Y_i (coefficient of x^i in GCM convention) 205 let y_bit = ((y[i / 8] >> (7 - (i % 8))) & 1) as u64; 206 let mask = 0u64.wrapping_sub(y_bit); 207 208 z_hi ^= v_hi & mask; 209 z_lo ^= v_lo & mask; 210 211 // Shift V right by 1 (toward higher powers); reduce if carry 212 let carry = v_lo & 1; 213 let carry_mask = 0u64.wrapping_sub(carry); 214 v_lo = (v_lo >> 1) | ((v_hi & 1) << 63); 215 v_hi = (v_hi >> 1) ^ (0xe100000000000000u64 & carry_mask); 216 } 217 218 let mut result = [0u8; 16]; 219 result[0..8].copy_from_slice(&z_hi.to_be_bytes()); 220 result[8..16].copy_from_slice(&z_lo.to_be_bytes()); 221 result 222} 223 224fn xor_block(a: &mut [u8; 16], b: &[u8; 16]) { 225 for i in 0..16 { 226 a[i] ^= b[i]; 227 } 228} 229 230/// Feed data blocks (with zero-padding of the final partial block) into GHASH. 231fn ghash_update(y: &mut [u8; 16], h: &[u8; 16], data: &[u8]) { 232 let mut offset = 0; 233 while offset + 16 <= data.len() { 234 let block: [u8; 16] = data[offset..offset + 16].try_into().unwrap(); 235 xor_block(y, &block); 236 *y = gf128_mul(y, h); 237 offset += 16; 238 } 239 if offset < data.len() { 240 let mut block = [0u8; 16]; 241 block[..data.len() - offset].copy_from_slice(&data[offset..]); 242 xor_block(y, &block); 243 *y = gf128_mul(y, h); 244 } 245} 246 247/// GHASH(H, AAD, C) = process AAD ∥ C ∥ lengths through the universal hash. 248fn ghash(h: &[u8; 16], aad: &[u8], ciphertext: &[u8]) -> [u8; 16] { 249 let mut y = [0u8; 16]; 250 251 ghash_update(&mut y, h, aad); 252 ghash_update(&mut y, h, ciphertext); 253 254 // Length block: [len(A) in bits]_64 ∥ [len(C) in bits]_64 255 let mut len_block = [0u8; 16]; 256 len_block[0..8].copy_from_slice(&((aad.len() as u64) * 8).to_be_bytes()); 257 len_block[8..16].copy_from_slice(&((ciphertext.len() as u64) * 8).to_be_bytes()); 258 xor_block(&mut y, &len_block); 259 y = gf128_mul(&y, h); 260 261 y 262} 263 264// --------------------------------------------------------------------------- 265// GCM counter 266// --------------------------------------------------------------------------- 267 268/// Increment the rightmost 32 bits of a 128-bit counter block. 269fn inc32(counter: &mut [u8; 16]) { 270 let c = u32::from_be_bytes(counter[12..16].try_into().unwrap()); 271 counter[12..16].copy_from_slice(&c.wrapping_add(1).to_be_bytes()); 272} 273 274// --------------------------------------------------------------------------- 275// Constant-time tag comparison 276// --------------------------------------------------------------------------- 277 278fn ct_eq_16(a: &[u8; 16], b: &[u8; 16]) -> bool { 279 let mut diff = 0u8; 280 for i in 0..16 { 281 diff |= a[i] ^ b[i]; 282 } 283 diff == 0 284} 285 286// --------------------------------------------------------------------------- 287// GCM core encrypt / decrypt 288// --------------------------------------------------------------------------- 289 290fn gcm_encrypt( 291 round_keys: &[[u8; 16]], 292 nonce: &[u8; 12], 293 plaintext: &[u8], 294 aad: &[u8], 295) -> (Vec<u8>, [u8; 16]) { 296 // H = E_K(0^128) 297 let h = aes_encrypt_block(&[0u8; 16], round_keys); 298 299 // J_0 = IV ∥ 0^31 ∥ 1 300 let mut j0 = [0u8; 16]; 301 j0[0..12].copy_from_slice(nonce); 302 j0[15] = 1; 303 304 // GCTR: encrypt plaintext with counter starting at inc32(J_0) 305 let mut counter = j0; 306 inc32(&mut counter); 307 308 let mut ciphertext = Vec::with_capacity(plaintext.len()); 309 let mut offset = 0; 310 while offset < plaintext.len() { 311 let keystream = aes_encrypt_block(&counter, round_keys); 312 let remaining = (plaintext.len() - offset).min(16); 313 for i in 0..remaining { 314 ciphertext.push(plaintext[offset + i] ^ keystream[i]); 315 } 316 offset += remaining; 317 inc32(&mut counter); 318 } 319 320 // Tag = GHASH(H, AAD, C) ⊕ E_K(J_0) 321 let s = ghash(&h, aad, &ciphertext); 322 let ek_j0 = aes_encrypt_block(&j0, round_keys); 323 let mut tag = [0u8; 16]; 324 for i in 0..16 { 325 tag[i] = s[i] ^ ek_j0[i]; 326 } 327 328 (ciphertext, tag) 329} 330 331fn gcm_decrypt( 332 round_keys: &[[u8; 16]], 333 nonce: &[u8; 12], 334 ciphertext: &[u8], 335 aad: &[u8], 336 tag: &[u8; 16], 337) -> Option<Vec<u8>> { 338 // H = E_K(0^128) 339 let h = aes_encrypt_block(&[0u8; 16], round_keys); 340 341 // J_0 = IV ∥ 0^31 ∥ 1 342 let mut j0 = [0u8; 16]; 343 j0[0..12].copy_from_slice(nonce); 344 j0[15] = 1; 345 346 // Verify tag before decrypting 347 let s = ghash(&h, aad, ciphertext); 348 let ek_j0 = aes_encrypt_block(&j0, round_keys); 349 let mut expected_tag = [0u8; 16]; 350 for i in 0..16 { 351 expected_tag[i] = s[i] ^ ek_j0[i]; 352 } 353 354 if !ct_eq_16(&expected_tag, tag) { 355 return None; 356 } 357 358 // GCTR: decrypt ciphertext 359 let mut counter = j0; 360 inc32(&mut counter); 361 362 let mut plaintext = Vec::with_capacity(ciphertext.len()); 363 let mut offset = 0; 364 while offset < ciphertext.len() { 365 let keystream = aes_encrypt_block(&counter, round_keys); 366 let remaining = (ciphertext.len() - offset).min(16); 367 for i in 0..remaining { 368 plaintext.push(ciphertext[offset + i] ^ keystream[i]); 369 } 370 offset += remaining; 371 inc32(&mut counter); 372 } 373 374 Some(plaintext) 375} 376 377// --------------------------------------------------------------------------- 378// Public API 379// --------------------------------------------------------------------------- 380 381/// AES-128-GCM encrypt. Returns `(ciphertext, 128-bit tag)`. 382pub fn aes128_gcm_encrypt( 383 key: &[u8; 16], 384 nonce: &[u8; 12], 385 plaintext: &[u8], 386 aad: &[u8], 387) -> (Vec<u8>, [u8; 16]) { 388 let rk = aes128_expand_key(key); 389 gcm_encrypt(&rk, nonce, plaintext, aad) 390} 391 392/// AES-128-GCM decrypt. Returns `None` if the authentication tag is invalid. 393pub fn aes128_gcm_decrypt( 394 key: &[u8; 16], 395 nonce: &[u8; 12], 396 ciphertext: &[u8], 397 aad: &[u8], 398 tag: &[u8; 16], 399) -> Option<Vec<u8>> { 400 let rk = aes128_expand_key(key); 401 gcm_decrypt(&rk, nonce, ciphertext, aad, tag) 402} 403 404/// AES-256-GCM encrypt. Returns `(ciphertext, 128-bit tag)`. 405pub fn aes256_gcm_encrypt( 406 key: &[u8; 32], 407 nonce: &[u8; 12], 408 plaintext: &[u8], 409 aad: &[u8], 410) -> (Vec<u8>, [u8; 16]) { 411 let rk = aes256_expand_key(key); 412 gcm_encrypt(&rk, nonce, plaintext, aad) 413} 414 415/// AES-256-GCM decrypt. Returns `None` if the authentication tag is invalid. 416pub fn aes256_gcm_decrypt( 417 key: &[u8; 32], 418 nonce: &[u8; 12], 419 ciphertext: &[u8], 420 aad: &[u8], 421 tag: &[u8; 16], 422) -> Option<Vec<u8>> { 423 let rk = aes256_expand_key(key); 424 gcm_decrypt(&rk, nonce, ciphertext, aad, tag) 425} 426 427// --------------------------------------------------------------------------- 428// Tests 429// --------------------------------------------------------------------------- 430 431#[cfg(test)] 432mod tests { 433 use super::*; 434 435 fn hex(bytes: &[u8]) -> String { 436 bytes.iter().map(|b| format!("{b:02x}")).collect() 437 } 438 439 fn from_hex(s: &str) -> Vec<u8> { 440 (0..s.len()) 441 .step_by(2) 442 .map(|i| u8::from_str_radix(&s[i..i + 2], 16).unwrap()) 443 .collect() 444 } 445 446 fn hex16(s: &str) -> [u8; 16] { 447 from_hex(s).try_into().unwrap() 448 } 449 450 // ----------------------------------------------------------------------- 451 // AES block cipher (ECB) — NIST test vectors 452 // ----------------------------------------------------------------------- 453 454 #[test] 455 fn aes128_ecb_zero_key() { 456 let key = [0u8; 16]; 457 let pt = [0u8; 16]; 458 let rk = aes128_expand_key(&key); 459 assert_eq!( 460 hex(&aes_encrypt_block(&pt, &rk)), 461 "66e94bd4ef8a2c3b884cfa59ca342b2e" 462 ); 463 } 464 465 #[test] 466 fn aes128_ecb_nist() { 467 let key = hex16("2b7e151628aed2a6abf7158809cf4f3c"); 468 let pt = hex16("6bc1bee22e409f96e93d7e117393172a"); 469 let rk = aes128_expand_key(&key); 470 assert_eq!( 471 hex(&aes_encrypt_block(&pt, &rk)), 472 "3ad77bb40d7a3660a89ecaf32466ef97" 473 ); 474 } 475 476 #[test] 477 fn aes256_ecb_zero_key() { 478 let key = [0u8; 32]; 479 let pt = [0u8; 16]; 480 let rk = aes256_expand_key(&key); 481 assert_eq!( 482 hex(&aes_encrypt_block(&pt, &rk)), 483 "dc95c078a2408989ad48a21492842087" 484 ); 485 } 486 487 #[test] 488 fn aes256_ecb_nist() { 489 let key: [u8; 32] = 490 from_hex("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4") 491 .try_into() 492 .unwrap(); 493 let pt = hex16("6bc1bee22e409f96e93d7e117393172a"); 494 let rk = aes256_expand_key(&key); 495 assert_eq!( 496 hex(&aes_encrypt_block(&pt, &rk)), 497 "f3eed1bdb5d2a03c064b5a7e3db181f8" 498 ); 499 } 500 501 // ----------------------------------------------------------------------- 502 // GCM Test Case 1: AES-128, empty plaintext, empty AAD 503 // ----------------------------------------------------------------------- 504 505 #[test] 506 fn gcm_128_case1_encrypt() { 507 let key = [0u8; 16]; 508 let nonce = [0u8; 12]; 509 let (ct, tag) = aes128_gcm_encrypt(&key, &nonce, &[], &[]); 510 assert!(ct.is_empty()); 511 assert_eq!(hex(&tag), "58e2fccefa7e3061367f1d57a4e7455a"); 512 } 513 514 #[test] 515 fn gcm_128_case1_decrypt() { 516 let key = [0u8; 16]; 517 let nonce = [0u8; 12]; 518 let tag = hex16("58e2fccefa7e3061367f1d57a4e7455a"); 519 let pt = aes128_gcm_decrypt(&key, &nonce, &[], &[], &tag).unwrap(); 520 assert!(pt.is_empty()); 521 } 522 523 // ----------------------------------------------------------------------- 524 // GCM Test Case 2: AES-128, 16-byte plaintext, empty AAD 525 // ----------------------------------------------------------------------- 526 527 #[test] 528 fn gcm_128_case2_encrypt() { 529 let key = [0u8; 16]; 530 let nonce = [0u8; 12]; 531 let pt = [0u8; 16]; 532 let (ct, tag) = aes128_gcm_encrypt(&key, &nonce, &pt, &[]); 533 assert_eq!(hex(&ct), "0388dace60b6a392f328c2b971b2fe78"); 534 assert_eq!(hex(&tag), "ab6e47d42cec13bdf53a67b21257bddf"); 535 } 536 537 #[test] 538 fn gcm_128_case2_decrypt() { 539 let key = [0u8; 16]; 540 let nonce = [0u8; 12]; 541 let ct = from_hex("0388dace60b6a392f328c2b971b2fe78"); 542 let tag = hex16("ab6e47d42cec13bdf53a67b21257bddf"); 543 let pt = aes128_gcm_decrypt(&key, &nonce, &ct, &[], &tag).unwrap(); 544 assert_eq!(pt, vec![0u8; 16]); 545 } 546 547 // ----------------------------------------------------------------------- 548 // GCM Test Case 3: AES-128, 64-byte plaintext, empty AAD 549 // ----------------------------------------------------------------------- 550 551 #[test] 552 fn gcm_128_case3_encrypt() { 553 let key = hex16("feffe9928665731c6d6a8f9467308308"); 554 let nonce: [u8; 12] = from_hex("cafebabefacedbaddecaf888").try_into().unwrap(); 555 let pt = from_hex( 556 "d9313225f88406e5a55909c5aff5269a\ 557 86a7a9531534f7da2e4c303d8a318a72\ 558 1c3c0c95956809532fcf0e2449a6b525\ 559 b16aedf5aa0de657ba637b391aafd255", 560 ); 561 let (ct, tag) = aes128_gcm_encrypt(&key, &nonce, &pt, &[]); 562 assert_eq!( 563 hex(&ct), 564 "42831ec2217774244b7221b784d0d49c\ 565 e3aa212f2c02a4e035c17e2329aca12e\ 566 21d514b25466931c7d8f6a5aac84aa05\ 567 1ba30b396a0aac973d58e091473f5985" 568 ); 569 assert_eq!(hex(&tag), "4d5c2af327cd64a62cf35abd2ba6fab4"); 570 } 571 572 #[test] 573 fn gcm_128_case3_decrypt() { 574 let key = hex16("feffe9928665731c6d6a8f9467308308"); 575 let nonce: [u8; 12] = from_hex("cafebabefacedbaddecaf888").try_into().unwrap(); 576 let ct = from_hex( 577 "42831ec2217774244b7221b784d0d49c\ 578 e3aa212f2c02a4e035c17e2329aca12e\ 579 21d514b25466931c7d8f6a5aac84aa05\ 580 1ba30b396a0aac973d58e091473f5985", 581 ); 582 let tag = hex16("4d5c2af327cd64a62cf35abd2ba6fab4"); 583 let pt = aes128_gcm_decrypt(&key, &nonce, &ct, &[], &tag).unwrap(); 584 assert_eq!( 585 hex(&pt), 586 "d9313225f88406e5a55909c5aff5269a\ 587 86a7a9531534f7da2e4c303d8a318a72\ 588 1c3c0c95956809532fcf0e2449a6b525\ 589 b16aedf5aa0de657ba637b391aafd255" 590 ); 591 } 592 593 // ----------------------------------------------------------------------- 594 // GCM Test Case 4: AES-128, 60-byte plaintext, 20-byte AAD 595 // ----------------------------------------------------------------------- 596 597 #[test] 598 fn gcm_128_case4_encrypt() { 599 let key = hex16("feffe9928665731c6d6a8f9467308308"); 600 let nonce: [u8; 12] = from_hex("cafebabefacedbaddecaf888").try_into().unwrap(); 601 let pt = from_hex( 602 "d9313225f88406e5a55909c5aff5269a\ 603 86a7a9531534f7da2e4c303d8a318a72\ 604 1c3c0c95956809532fcf0e2449a6b525\ 605 b16aedf5aa0de657ba637b39", 606 ); 607 let aad = from_hex("feedfacedeadbeeffeedfacedeadbeefabaddad2"); 608 let (ct, tag) = aes128_gcm_encrypt(&key, &nonce, &pt, &aad); 609 assert_eq!( 610 hex(&ct), 611 "42831ec2217774244b7221b784d0d49c\ 612 e3aa212f2c02a4e035c17e2329aca12e\ 613 21d514b25466931c7d8f6a5aac84aa05\ 614 1ba30b396a0aac973d58e091", 615 ); 616 assert_eq!(hex(&tag), "5bc94fbc3221a5db94fae95ae7121a47"); 617 } 618 619 #[test] 620 fn gcm_128_case4_decrypt() { 621 let key = hex16("feffe9928665731c6d6a8f9467308308"); 622 let nonce: [u8; 12] = from_hex("cafebabefacedbaddecaf888").try_into().unwrap(); 623 let ct = from_hex( 624 "42831ec2217774244b7221b784d0d49c\ 625 e3aa212f2c02a4e035c17e2329aca12e\ 626 21d514b25466931c7d8f6a5aac84aa05\ 627 1ba30b396a0aac973d58e091", 628 ); 629 let aad = from_hex("feedfacedeadbeeffeedfacedeadbeefabaddad2"); 630 let tag = hex16("5bc94fbc3221a5db94fae95ae7121a47"); 631 let pt = aes128_gcm_decrypt(&key, &nonce, &ct, &aad, &tag).unwrap(); 632 assert_eq!( 633 hex(&pt), 634 "d9313225f88406e5a55909c5aff5269a\ 635 86a7a9531534f7da2e4c303d8a318a72\ 636 1c3c0c95956809532fcf0e2449a6b525\ 637 b16aedf5aa0de657ba637b39" 638 ); 639 } 640 641 // ----------------------------------------------------------------------- 642 // GCM Test Case 13: AES-256, empty plaintext, empty AAD 643 // ----------------------------------------------------------------------- 644 645 #[test] 646 fn gcm_256_case13_encrypt() { 647 let key = [0u8; 32]; 648 let nonce = [0u8; 12]; 649 let (ct, tag) = aes256_gcm_encrypt(&key, &nonce, &[], &[]); 650 assert!(ct.is_empty()); 651 assert_eq!(hex(&tag), "530f8afbc74536b9a963b4f1c4cb738b"); 652 } 653 654 #[test] 655 fn gcm_256_case13_decrypt() { 656 let key = [0u8; 32]; 657 let nonce = [0u8; 12]; 658 let tag = hex16("530f8afbc74536b9a963b4f1c4cb738b"); 659 let pt = aes256_gcm_decrypt(&key, &nonce, &[], &[], &tag).unwrap(); 660 assert!(pt.is_empty()); 661 } 662 663 // ----------------------------------------------------------------------- 664 // GCM Test Case 14: AES-256, 16-byte plaintext, empty AAD 665 // ----------------------------------------------------------------------- 666 667 #[test] 668 fn gcm_256_case14_encrypt() { 669 let key = [0u8; 32]; 670 let nonce = [0u8; 12]; 671 let pt = [0u8; 16]; 672 let (ct, tag) = aes256_gcm_encrypt(&key, &nonce, &pt, &[]); 673 assert_eq!(hex(&ct), "cea7403d4d606b6e074ec5d3baf39d18"); 674 assert_eq!(hex(&tag), "d0d1c8a799996bf0265b98b5d48ab919"); 675 } 676 677 #[test] 678 fn gcm_256_case14_decrypt() { 679 let key = [0u8; 32]; 680 let nonce = [0u8; 12]; 681 let ct = from_hex("cea7403d4d606b6e074ec5d3baf39d18"); 682 let tag = hex16("d0d1c8a799996bf0265b98b5d48ab919"); 683 let pt = aes256_gcm_decrypt(&key, &nonce, &ct, &[], &tag).unwrap(); 684 assert_eq!(pt, vec![0u8; 16]); 685 } 686 687 // ----------------------------------------------------------------------- 688 // GCM Test Case 15: AES-256, 64-byte plaintext, empty AAD 689 // ----------------------------------------------------------------------- 690 691 #[test] 692 fn gcm_256_case15_encrypt() { 693 let key: [u8; 32] = 694 from_hex("feffe9928665731c6d6a8f9467308308feffe9928665731c6d6a8f9467308308") 695 .try_into() 696 .unwrap(); 697 let nonce: [u8; 12] = from_hex("cafebabefacedbaddecaf888").try_into().unwrap(); 698 let pt = from_hex( 699 "d9313225f88406e5a55909c5aff5269a\ 700 86a7a9531534f7da2e4c303d8a318a72\ 701 1c3c0c95956809532fcf0e2449a6b525\ 702 b16aedf5aa0de657ba637b391aafd255", 703 ); 704 let (ct, tag) = aes256_gcm_encrypt(&key, &nonce, &pt, &[]); 705 assert_eq!( 706 hex(&ct), 707 "522dc1f099567d07f47f37a32a84427d\ 708 643a8cdcbfe5c0c97598a2bd2555d1aa\ 709 8cb08e48590dbb3da7b08b1056828838\ 710 c5f61e6393ba7a0abcc9f662898015ad" 711 ); 712 assert_eq!(hex(&tag), "b094dac5d93471bdec1a502270e3cc6c"); 713 } 714 715 #[test] 716 fn gcm_256_case15_decrypt() { 717 let key: [u8; 32] = 718 from_hex("feffe9928665731c6d6a8f9467308308feffe9928665731c6d6a8f9467308308") 719 .try_into() 720 .unwrap(); 721 let nonce: [u8; 12] = from_hex("cafebabefacedbaddecaf888").try_into().unwrap(); 722 let ct = from_hex( 723 "522dc1f099567d07f47f37a32a84427d\ 724 643a8cdcbfe5c0c97598a2bd2555d1aa\ 725 8cb08e48590dbb3da7b08b1056828838\ 726 c5f61e6393ba7a0abcc9f662898015ad", 727 ); 728 let tag = hex16("b094dac5d93471bdec1a502270e3cc6c"); 729 let pt = aes256_gcm_decrypt(&key, &nonce, &ct, &[], &tag).unwrap(); 730 assert_eq!( 731 hex(&pt), 732 "d9313225f88406e5a55909c5aff5269a\ 733 86a7a9531534f7da2e4c303d8a318a72\ 734 1c3c0c95956809532fcf0e2449a6b525\ 735 b16aedf5aa0de657ba637b391aafd255" 736 ); 737 } 738 739 // ----------------------------------------------------------------------- 740 // GCM Test Case 16: AES-256, 60-byte plaintext, 20-byte AAD 741 // ----------------------------------------------------------------------- 742 743 #[test] 744 fn gcm_256_case16_encrypt() { 745 let key: [u8; 32] = 746 from_hex("feffe9928665731c6d6a8f9467308308feffe9928665731c6d6a8f9467308308") 747 .try_into() 748 .unwrap(); 749 let nonce: [u8; 12] = from_hex("cafebabefacedbaddecaf888").try_into().unwrap(); 750 let pt = from_hex( 751 "d9313225f88406e5a55909c5aff5269a\ 752 86a7a9531534f7da2e4c303d8a318a72\ 753 1c3c0c95956809532fcf0e2449a6b525\ 754 b16aedf5aa0de657ba637b39", 755 ); 756 let aad = from_hex("feedfacedeadbeeffeedfacedeadbeefabaddad2"); 757 let (ct, tag) = aes256_gcm_encrypt(&key, &nonce, &pt, &aad); 758 assert_eq!( 759 hex(&ct), 760 "522dc1f099567d07f47f37a32a84427d\ 761 643a8cdcbfe5c0c97598a2bd2555d1aa\ 762 8cb08e48590dbb3da7b08b1056828838\ 763 c5f61e6393ba7a0abcc9f662", 764 ); 765 assert_eq!(hex(&tag), "76fc6ece0f4e1768cddf8853bb2d551b"); 766 } 767 768 #[test] 769 fn gcm_256_case16_decrypt() { 770 let key: [u8; 32] = 771 from_hex("feffe9928665731c6d6a8f9467308308feffe9928665731c6d6a8f9467308308") 772 .try_into() 773 .unwrap(); 774 let nonce: [u8; 12] = from_hex("cafebabefacedbaddecaf888").try_into().unwrap(); 775 let ct = from_hex( 776 "522dc1f099567d07f47f37a32a84427d\ 777 643a8cdcbfe5c0c97598a2bd2555d1aa\ 778 8cb08e48590dbb3da7b08b1056828838\ 779 c5f61e6393ba7a0abcc9f662", 780 ); 781 let aad = from_hex("feedfacedeadbeeffeedfacedeadbeefabaddad2"); 782 let tag = hex16("76fc6ece0f4e1768cddf8853bb2d551b"); 783 let pt = aes256_gcm_decrypt(&key, &nonce, &ct, &aad, &tag).unwrap(); 784 assert_eq!( 785 hex(&pt), 786 "d9313225f88406e5a55909c5aff5269a\ 787 86a7a9531534f7da2e4c303d8a318a72\ 788 1c3c0c95956809532fcf0e2449a6b525\ 789 b16aedf5aa0de657ba637b39" 790 ); 791 } 792 793 // ----------------------------------------------------------------------- 794 // Tag tamper detection 795 // ----------------------------------------------------------------------- 796 797 #[test] 798 fn decrypt_rejects_tampered_tag() { 799 let key = [0u8; 16]; 800 let nonce = [0u8; 12]; 801 let pt = b"hello world"; 802 let (ct, mut tag) = aes128_gcm_encrypt(&key, &nonce, pt, &[]); 803 tag[0] ^= 1; 804 assert!(aes128_gcm_decrypt(&key, &nonce, &ct, &[], &tag).is_none()); 805 } 806 807 #[test] 808 fn decrypt_rejects_tampered_ciphertext() { 809 let key = [0u8; 16]; 810 let nonce = [0u8; 12]; 811 let pt = b"hello world"; 812 let (mut ct, tag) = aes128_gcm_encrypt(&key, &nonce, pt, &[]); 813 ct[0] ^= 1; 814 assert!(aes128_gcm_decrypt(&key, &nonce, &ct, &[], &tag).is_none()); 815 } 816 817 #[test] 818 fn decrypt_rejects_tampered_aad() { 819 let key = [0u8; 16]; 820 let nonce = [0u8; 12]; 821 let pt = b"hello world"; 822 let aad = b"associated data"; 823 let (ct, tag) = aes128_gcm_encrypt(&key, &nonce, pt, aad); 824 let bad_aad = b"Associated data"; 825 assert!(aes128_gcm_decrypt(&key, &nonce, &ct, bad_aad, &tag).is_none()); 826 } 827 828 // ----------------------------------------------------------------------- 829 // Round-trip 830 // ----------------------------------------------------------------------- 831 832 #[test] 833 fn aes128_gcm_roundtrip() { 834 let key = hex16("deadbeefdeadbeefdeadbeefdeadbeef"); 835 let nonce: [u8; 12] = from_hex("0102030405060708090a0b0c").try_into().unwrap(); 836 let pt = b"The quick brown fox jumps over the lazy dog"; 837 let aad = b"additional authenticated data"; 838 let (ct, tag) = aes128_gcm_encrypt(&key, &nonce, pt, aad); 839 let recovered = aes128_gcm_decrypt(&key, &nonce, &ct, aad, &tag).unwrap(); 840 assert_eq!(recovered, pt); 841 } 842 843 #[test] 844 fn aes256_gcm_roundtrip() { 845 let key: [u8; 32] = 846 from_hex("deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef") 847 .try_into() 848 .unwrap(); 849 let nonce: [u8; 12] = from_hex("0102030405060708090a0b0c").try_into().unwrap(); 850 let pt = b"The quick brown fox jumps over the lazy dog"; 851 let aad = b"additional authenticated data"; 852 let (ct, tag) = aes256_gcm_encrypt(&key, &nonce, pt, aad); 853 let recovered = aes256_gcm_decrypt(&key, &nonce, &ct, aad, &tag).unwrap(); 854 assert_eq!(recovered, pt); 855 } 856}