we (web engine): Experimental web browser project to understand the limits of Claude
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}