/ test-server / ghash_test.rs
ghash_test.rs
  1  // Standalone GHASH test - corrected implementation
  2  // Run with: rustc ghash_test.rs -o ghash_test && ./ghash_test
  3  
  4  use std::vec::Vec;
  5  
  6  // ============= GHASH implementation (corrected for GCM bit ordering) =============
  7  
  8  /// GHASH implementation for GCM
  9  /// GCM uses a "reflected" bit ordering where MSB of byte 0 is x^0
 10  struct Ghash {
 11      h: [u8; 16],
 12      state: [u8; 16],
 13  }
 14  
 15  impl Ghash {
 16      fn new(h: &[u8; 16]) -> Self {
 17          Ghash {
 18              h: *h,
 19              state: [0u8; 16],
 20          }
 21      }
 22  
 23      fn update(&mut self, block: &[u8; 16]) {
 24          for i in 0..16 {
 25              self.state[i] ^= block[i];
 26          }
 27          self.state = Self::gf_mul(&self.state, &self.h);
 28      }
 29  
 30      /// GF(2^128) multiplication in GCM's reflected representation
 31      fn gf_mul(x: &[u8; 16], y: &[u8; 16]) -> [u8; 16] {
 32          let mut z = [0u8; 16];
 33          let mut v = *y;
 34  
 35          for i in 0..128 {
 36              let byte_idx = i / 8;
 37              let bit_idx = 7 - (i % 8);
 38  
 39              if (x[byte_idx] >> bit_idx) & 1 == 1 {
 40                  for j in 0..16 {
 41                      z[j] ^= v[j];
 42                  }
 43              }
 44  
 45              let lsb = v[15] & 1;
 46  
 47              // Right shift V by 1 bit
 48              let mut carry = 0u8;
 49              for j in 0..16 {
 50                  let new_carry = v[j] & 1;
 51                  v[j] = (v[j] >> 1) | (carry << 7);
 52                  carry = new_carry;
 53              }
 54  
 55              if lsb == 1 {
 56                  v[0] ^= 0xe1;
 57              }
 58          }
 59  
 60          z
 61      }
 62  
 63      fn finalize(self) -> [u8; 16] {
 64          self.state
 65      }
 66  }
 67  
 68  // ============= Simple AES for testing =============
 69  
 70  struct Aes128 {
 71      round_keys: [[u8; 16]; 11],
 72  }
 73  
 74  impl Aes128 {
 75      fn new(key: &[u8; 16]) -> Self {
 76          let mut round_keys = [[0u8; 16]; 11];
 77          round_keys[0].copy_from_slice(key);
 78  
 79          let mut temp = [0u8; 4];
 80          for i in 1..11 {
 81              temp.copy_from_slice(&round_keys[i-1][12..16]);
 82  
 83              let t = temp[0];
 84              temp[0] = temp[1];
 85              temp[1] = temp[2];
 86              temp[2] = temp[3];
 87              temp[3] = t;
 88  
 89              for b in temp.iter_mut() {
 90                  *b = SBOX[*b as usize];
 91              }
 92  
 93              temp[0] ^= RCON[i];
 94  
 95              for j in 0..4 {
 96                  round_keys[i][j] = round_keys[i-1][j] ^ temp[j];
 97              }
 98              for j in 1..4 {
 99                  for k in 0..4 {
100                      round_keys[i][j*4 + k] = round_keys[i-1][j*4 + k] ^ round_keys[i][(j-1)*4 + k];
101                  }
102              }
103          }
104  
105          Self { round_keys }
106      }
107  
108      fn encrypt_block(&self, block: &mut [u8; 16]) {
109          for i in 0..16 {
110              block[i] ^= self.round_keys[0][i];
111          }
112  
113          for round in 1..10 {
114              for b in block.iter_mut() {
115                  *b = SBOX[*b as usize];
116              }
117  
118              let temp = *block;
119              block[1] = temp[5];
120              block[5] = temp[9];
121              block[9] = temp[13];
122              block[13] = temp[1];
123              block[2] = temp[10];
124              block[6] = temp[14];
125              block[10] = temp[2];
126              block[14] = temp[6];
127              block[3] = temp[15];
128              block[7] = temp[3];
129              block[11] = temp[7];
130              block[15] = temp[11];
131  
132              for i in 0..4 {
133                  let col = i * 4;
134                  let a = block[col];
135                  let b = block[col + 1];
136                  let c = block[col + 2];
137                  let d = block[col + 3];
138  
139                  block[col] = gmul(a, 2) ^ gmul(b, 3) ^ c ^ d;
140                  block[col + 1] = a ^ gmul(b, 2) ^ gmul(c, 3) ^ d;
141                  block[col + 2] = a ^ b ^ gmul(c, 2) ^ gmul(d, 3);
142                  block[col + 3] = gmul(a, 3) ^ b ^ c ^ gmul(d, 2);
143              }
144  
145              for i in 0..16 {
146                  block[i] ^= self.round_keys[round][i];
147              }
148          }
149  
150          for b in block.iter_mut() {
151              *b = SBOX[*b as usize];
152          }
153  
154          let temp = *block;
155          block[1] = temp[5];
156          block[5] = temp[9];
157          block[9] = temp[13];
158          block[13] = temp[1];
159          block[2] = temp[10];
160          block[6] = temp[14];
161          block[10] = temp[2];
162          block[14] = temp[6];
163          block[3] = temp[15];
164          block[7] = temp[3];
165          block[11] = temp[7];
166          block[15] = temp[11];
167  
168          for i in 0..16 {
169              block[i] ^= self.round_keys[10][i];
170          }
171      }
172  }
173  
174  fn gmul(a: u8, b: u8) -> u8 {
175      let mut p = 0u8;
176      let mut a = a;
177      let mut b = b;
178      for _ in 0..8 {
179          if b & 1 != 0 {
180              p ^= a;
181          }
182          let hi_bit_set = a & 0x80 != 0;
183          a <<= 1;
184          if hi_bit_set {
185              a ^= 0x1b;
186          }
187          b >>= 1;
188      }
189      p
190  }
191  
192  static SBOX: [u8; 256] = [
193      0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76,
194      0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0,
195      0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15,
196      0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75,
197      0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84,
198      0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf,
199      0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8,
200      0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2,
201      0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73,
202      0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb,
203      0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79,
204      0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08,
205      0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a,
206      0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e,
207      0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf,
208      0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16,
209  ];
210  
211  static RCON: [u8; 11] = [0x00, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36];
212  
213  // ============= AES-GCM =============
214  
215  struct AesGcm {
216      cipher: Aes128,
217      h: [u8; 16],
218  }
219  
220  impl AesGcm {
221      fn new(key: &[u8; 16]) -> Self {
222          let cipher = Aes128::new(key);
223          let mut h = [0u8; 16];
224          cipher.encrypt_block(&mut h);
225          AesGcm { cipher, h }
226      }
227  
228      fn encrypt(&self, nonce: &[u8; 12], aad: &[u8], plaintext: &[u8]) -> Vec<u8> {
229          let mut ghash = Ghash::new(&self.h);
230  
231          // Process AAD
232          self.ghash_update_padded(&mut ghash, aad);
233  
234          // Encrypt plaintext
235          let mut ciphertext = Vec::with_capacity(plaintext.len() + 16);
236          let mut ctr = 2u32;
237  
238          for chunk in plaintext.chunks(16) {
239              let mut counter_block = [0u8; 16];
240              counter_block[0..12].copy_from_slice(nonce);
241              counter_block[12..16].copy_from_slice(&ctr.to_be_bytes());
242  
243              self.cipher.encrypt_block(&mut counter_block);
244  
245              let mut ct_block = [0u8; 16];
246              for (i, &b) in chunk.iter().enumerate() {
247                  ct_block[i] = counter_block[i] ^ b;
248              }
249              ciphertext.extend_from_slice(&ct_block[..chunk.len()]);
250              ctr += 1;
251          }
252  
253          // Process ciphertext through GHASH
254          self.ghash_update_padded(&mut ghash, &ciphertext);
255  
256          // Append lengths block
257          let aad_bits = (aad.len() as u64) * 8;
258          let ct_bits = (ciphertext.len() as u64) * 8;
259          let mut len_block = [0u8; 16];
260          len_block[0..8].copy_from_slice(&aad_bits.to_be_bytes());
261          len_block[8..16].copy_from_slice(&ct_bits.to_be_bytes());
262          ghash.update(&len_block);
263  
264          // Generate tag
265          let mut tag_mask = [0u8; 16];
266          tag_mask[0..12].copy_from_slice(nonce);
267          tag_mask[12..16].copy_from_slice(&1u32.to_be_bytes());
268          self.cipher.encrypt_block(&mut tag_mask);
269  
270          let ghash_result = ghash.finalize();
271          let mut tag = [0u8; 16];
272          for i in 0..16 {
273              tag[i] = ghash_result[i] ^ tag_mask[i];
274          }
275  
276          ciphertext.extend_from_slice(&tag);
277          ciphertext
278      }
279  
280      fn ghash_update_padded(&self, ghash: &mut Ghash, data: &[u8]) {
281          for chunk in data.chunks(16) {
282              let mut block = [0u8; 16];
283              block[..chunk.len()].copy_from_slice(chunk);
284              ghash.update(&block);
285          }
286      }
287  }
288  
289  // ============= Tests =============
290  
291  fn hex_decode(s: &str) -> Vec<u8> {
292      (0..s.len())
293          .step_by(2)
294          .map(|i| u8::from_str_radix(&s[i..i + 2], 16).unwrap())
295          .collect()
296  }
297  
298  fn hex_encode(data: &[u8]) -> String {
299      data.iter().map(|b| format!("{:02x}", b)).collect()
300  }
301  
302  fn main() {
303      println!("=== GHASH/AES-GCM Test Suite (Corrected) ===\n");
304  
305      let mut passed = 0;
306      let mut failed = 0;
307  
308      // Test 1: Empty plaintext, empty AAD
309      print!("TC1: Empty PT, Empty AAD... ");
310      {
311          let key = [0u8; 16];
312          let nonce = [0u8; 12];
313          let gcm = AesGcm::new(&key);
314          let result = gcm.encrypt(&nonce, &[], &[]);
315          let expected_tag = hex_decode("58e2fccefa7e3061367f1d57a4e7455a");
316  
317          if &result[..] == &expected_tag[..] {
318              println!("PASS");
319              passed += 1;
320          } else {
321              println!("FAIL");
322              println!("  Got:      {}", hex_encode(&result));
323              println!("  Expected: {}", hex_encode(&expected_tag));
324              failed += 1;
325          }
326      }
327  
328      // Test 2: 16-byte plaintext, empty AAD
329      print!("TC2: 16B PT, Empty AAD... ");
330      {
331          let key = [0u8; 16];
332          let nonce = [0u8; 12];
333          let plaintext = [0u8; 16];
334          let gcm = AesGcm::new(&key);
335          let result = gcm.encrypt(&nonce, &[], &plaintext);
336  
337          let expected_ct = hex_decode("0388dace60b6a392f328c2b971b2fe78");
338          let expected_tag = hex_decode("ab6e47d42cec13bdf53a67b21257bddf");
339  
340          let ct_ok = &result[..16] == &expected_ct[..];
341          let tag_ok = &result[16..] == &expected_tag[..];
342  
343          if ct_ok && tag_ok {
344              println!("PASS");
345              passed += 1;
346          } else {
347              println!("FAIL");
348              if !ct_ok {
349                  println!("  CT Got:      {}", hex_encode(&result[..16]));
350                  println!("  CT Expected: {}", hex_encode(&expected_ct));
351              }
352              if !tag_ok {
353                  println!("  Tag Got:      {}", hex_encode(&result[16..]));
354                  println!("  Tag Expected: {}", hex_encode(&expected_tag));
355              }
356              failed += 1;
357          }
358      }
359  
360      // Test 3: 64-byte plaintext, empty AAD
361      print!("TC3: 64B PT, Empty AAD... ");
362      {
363          let key_bytes = hex_decode("feffe9928665731c6d6a8f9467308308");
364          let mut key = [0u8; 16];
365          key.copy_from_slice(&key_bytes);
366  
367          let nonce_bytes = hex_decode("cafebabefacedbaddecaf888");
368          let mut nonce = [0u8; 12];
369          nonce.copy_from_slice(&nonce_bytes);
370  
371          let plaintext = hex_decode(
372              "d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b391aafd255"
373          );
374  
375          let gcm = AesGcm::new(&key);
376          let result = gcm.encrypt(&nonce, &[], &plaintext);
377  
378          let expected_ct = hex_decode(
379              "42831ec2217774244b7221b784d0d49ce3aa212f2c02a4e035c17e2329aca12e21d514b25466931c7d8f6a5aac84aa051ba30b396a0aac973d58e091473f5985"
380          );
381          let expected_tag = hex_decode("4d5c2af327cd64a62cf35abd2ba6fab4");
382  
383          let ct_ok = &result[..64] == &expected_ct[..];
384          let tag_ok = &result[64..] == &expected_tag[..];
385  
386          if ct_ok && tag_ok {
387              println!("PASS");
388              passed += 1;
389          } else {
390              println!("FAIL");
391              if !ct_ok {
392                  println!("  CT Got:      {}", hex_encode(&result[..64]));
393                  println!("  CT Expected: {}", hex_encode(&expected_ct));
394              }
395              if !tag_ok {
396                  println!("  Tag Got:      {}", hex_encode(&result[64..]));
397                  println!("  Tag Expected: {}", hex_encode(&expected_tag));
398              }
399              failed += 1;
400          }
401      }
402  
403      // Test 4: 60-byte plaintext, 20-byte AAD
404      print!("TC4: 60B PT, 20B AAD... ");
405      {
406          let key_bytes = hex_decode("feffe9928665731c6d6a8f9467308308");
407          let mut key = [0u8; 16];
408          key.copy_from_slice(&key_bytes);
409  
410          let nonce_bytes = hex_decode("cafebabefacedbaddecaf888");
411          let mut nonce = [0u8; 12];
412          nonce.copy_from_slice(&nonce_bytes);
413  
414          let aad = hex_decode("feedfacedeadbeeffeedfacedeadbeefabaddad2");
415          let plaintext = hex_decode(
416              "d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b39"
417          );
418  
419          let gcm = AesGcm::new(&key);
420          let result = gcm.encrypt(&nonce, &aad, &plaintext);
421  
422          let expected_ct = hex_decode(
423              "42831ec2217774244b7221b784d0d49ce3aa212f2c02a4e035c17e2329aca12e21d514b25466931c7d8f6a5aac84aa051ba30b396a0aac973d58e091"
424          );
425          let expected_tag = hex_decode("5bc94fbc3221a5db94fae95ae7121a47");
426  
427          let ct_ok = &result[..60] == &expected_ct[..];
428          let tag_ok = &result[60..] == &expected_tag[..];
429  
430          if ct_ok && tag_ok {
431              println!("PASS");
432              passed += 1;
433          } else {
434              println!("FAIL");
435              if !ct_ok {
436                  println!("  CT Got:      {}", hex_encode(&result[..60]));
437                  println!("  CT Expected: {}", hex_encode(&expected_ct));
438              }
439              if !tag_ok {
440                  println!("  Tag Got:      {}", hex_encode(&result[60..]));
441                  println!("  Tag Expected: {}", hex_encode(&expected_tag));
442              }
443              failed += 1;
444          }
445      }
446  
447      // Test 5: QUIC-like 1157 bytes
448      print!("TC5: 1157B PT, 26B AAD (QUIC-like)... ");
449      {
450          let key_bytes = hex_decode("1f369613dd76d5467730efcbe3b1a22d");
451          let mut key = [0u8; 16];
452          key.copy_from_slice(&key_bytes);
453  
454          let nonce_bytes = hex_decode("fa044b2f42a3fd3b46fb255c");
455          let mut nonce = [0u8; 12];
456          nonce.copy_from_slice(&nonce_bytes);
457  
458          let aad = hex_decode("c3000000018394c8f03e51570800000000000000000000000000");
459  
460          let mut plaintext = vec![0u8; 1157];
461          for (i, byte) in plaintext.iter_mut().enumerate() {
462              *byte = (i & 0xff) as u8;
463          }
464  
465          let gcm = AesGcm::new(&key);
466          let result = gcm.encrypt(&nonce, &aad, &plaintext);
467  
468          // From Go reference
469          let expected_ct_start = hex_decode("40b513d99e79a520cbaf459eca8579cc");
470          let expected_tag = hex_decode("58acb131f857b7192188751449aa04bb");
471  
472          let ct_start_ok = &result[..16] == &expected_ct_start[..];
473          let tag_ok = &result[1157..] == &expected_tag[..];
474  
475          if ct_start_ok && tag_ok {
476              println!("PASS");
477              passed += 1;
478          } else {
479              println!("FAIL");
480              if !ct_start_ok {
481                  println!("  CT[0:16] Got:      {}", hex_encode(&result[..16]));
482                  println!("  CT[0:16] Expected: {}", hex_encode(&expected_ct_start));
483              }
484              if !tag_ok {
485                  println!("  Tag Got:      {}", hex_encode(&result[1157..]));
486                  println!("  Tag Expected: {}", hex_encode(&expected_tag));
487              }
488              failed += 1;
489          }
490      }
491  
492      println!("\n=== Results: {} passed, {} failed ===", passed, failed);
493  
494      if failed > 0 {
495          std::process::exit(1);
496      }
497  }