AES-GCM: pending fixes & extend tests (NIST testvectors & external lib)
arnaucube git@arnaucube.com
Thu, 12 Feb 2026 06:17:02 +0000
2 files changed,
106 insertions(+),
72 deletions(-)
M
Cargo.toml
→
Cargo.toml
@@ -11,3 +11,4 @@ [dev-dependencies]
rand = "0.10.0" aes = "0.8.4" aes-gcm = "0.10.3" +hex = "0.4.3"
M
src/native_gcm.rs
→
src/native_gcm.rs
@@ -7,7 +7,7 @@ //! is not written in the most rust idiomatic way, nor memory-efficient, but
//! simulating the behavior that we will do later inside the circuit. //! -use crate::native_aes::{encrypt_block, flatten_state, key_expansion, State}; +use crate::native_aes::{encrypt_block, flatten_state, key_expansion}; // supported tag length const TAG_LEN: usize = 128;@@ -22,7 +22,7 @@ where
[(); 4 * (NR + 1)]:, { let a: &[u8] = &[]; // additional authenticated data // TODO maybe as input - let expanded_key: [[u8; 4]; 4 * (NR + 1)] = key_expansion::<NK, NB, NR>(&key); + let expanded_key: [[u8; 4]; 4 * (NR + 1)] = key_expansion::<NK, NB, NR>(key); // 1. CIPH_K let h = encrypt_block::<NR>(&[0u8; 16], &expanded_key);@@ -32,7 +32,7 @@ // 2. J_0
let j0: [u8; 16] = if nonce.len() * 8 == 96 { // J_0 = IV || 0^31 || 1 let mut out = [0u8; 16]; - out[..12].copy_from_slice(&nonce.as_slice()); + out[..12].copy_from_slice(nonce.as_slice()); out[12..16].copy_from_slice(&[0x00, 0x00, 0x00, 0x01]); out } else {@@ -40,12 +40,11 @@ panic!("unsuported at initial version; nonce.len()=12 (96 bits)");
}; // 3. C=GCTR() - let c = gctr(expanded_key, &inc32(j0), &pt); + let c = gctr(expanded_key, &inc32(j0), pt); // 4. u, v - // let u = (16 - ceil(c.len() % 16)) % 16 // TODO WIP - let u: usize = 128 * ((c.len() * 8) as f64 / 128_f64).ceil() as usize - c.len() * 8; - let v: usize = 128 * ((a.len() * 8) as f64 / 128_f64).ceil() as usize - a.len() * 8; + let u: usize = 16 * (c.len() as f64 / 16_f64).ceil() as usize - c.len(); + let v: usize = 16 * (a.len() as f64 / 16_f64).ceil() as usize - a.len(); // 5. S = GHASH() let a_len: [u8; 8] = (a.len() * 8).to_be_bytes();@@ -75,11 +74,10 @@ }
let n = ((x.len() * 8) as f64 / 128_f64).ceil() as usize; - let mut y = vec![]; - let mut cb_i = icb.clone(); + let mut y: Vec<Vec<u8>> = vec![]; + let mut cb_i = *icb; for (i, x_i_raw) in x.chunks(16).enumerate() { - // 5. - if i > 1 { + if i > 0 { cb_i = inc32(cb_i); }@@ -89,24 +87,24 @@ x_i[..l].copy_from_slice(x_i_raw);
let ciph_cb_i = flatten_state(encrypt_block::<NR>(&cb_i, &key)); - let y_i = if i < n { - xor_blocks(x_i, ciph_cb_i) + let (y_i, n_bytes) = if i < n && x_i_raw.len() == 16 { + (xor_blocks(x_i, ciph_cb_i), 16) } else { // last chunk, might be smaller than 16 bytes - let msb_res = msb_t(x_i.len(), ciph_cb_i.as_slice()); + let msb_res = msb_t(x_i_raw.len(), ciph_cb_i.as_slice()); let l = msb_res.len().min(16); let mut m = [0u8; 16]; m[..l].copy_from_slice(&msb_res); - xor_blocks(x_i, m) + (xor_blocks(x_i, m), l) }; - y.push(y_i); + y.push(y_i[..n_bytes].to_vec()); } y.concat() } /// Section 6.4, Algorithm 2 fn ghash(h: [u8; 16], x: &[u8]) -> [u8; 16] { - assert!(x.len() % 16 == 0); // multiple of 128 bits + assert!(x.len().is_multiple_of(16)); // multiple of 128 bits let m = x.len() / 16; let mut y = [0u8; 16]; // (128 bits)@@ -165,7 +163,7 @@ }
/// increment the right-most 32 bits of the given block (128 bits). Section 2. pub fn inc32(b: [u8; 16]) -> [u8; 16] { - let mut r = b.clone(); + let mut r = b; // counter = last 32bits let counter = u32::from_be_bytes([r[12], r[13], r[14], r[15]]); let incremented = counter.wrapping_add(1);@@ -175,10 +173,10 @@ r
} /// returns the t left-most (most-significant in BE) bits of the block -pub fn msb_t(t: usize, block: &[u8]) -> Vec<u8> { - assert!(t <= 128, "MSB_t err: max 128 bits"); +pub fn msb_t(t_bytes: usize, block: &[u8]) -> Vec<u8> { + let t = t_bytes * 8; - let mut out = Vec::with_capacity((t + 7) / 8); // ceiling bytes + let mut out = Vec::with_capacity(t.div_ceil(8)); let mut bits_left = t; for byte in block.iter() { if bits_left == 0 {@@ -202,77 +200,112 @@ mod tests {
use super::*; use aes_gcm::{ - aead::{Aead, AeadCore, Key, KeyInit, OsRng}, - Aes128Gcm, Nonce, + aead::{Aead, KeyInit}, + Aes128Gcm, Aes256Gcm, Nonce, }; + use rand::Rng; + + fn hex_to_array<const N: usize>(h: &str) -> [u8; N] { + let bytes = hex::decode(h).unwrap(); + assert_eq!(bytes.len(), N); + let mut r: [u8; N] = [0u8; N]; + r.copy_from_slice(&bytes); + r + } #[test] fn test_aes_gcm_encrypt_nist_vector() { // https://csrc.nist.gov/Projects/Cryptographic-Algorithm-Validation-Program/CAVP-TESTING-BLOCK-CIPHER-MODES - // test vector at line 7 - let key: [u8; 16] = [ - 0xcf, 0x06, 0x3a, 0x34, 0xd4, 0xa9, 0xa7, 0x6c, 0x2c, 0x86, 0x78, 0x7d, 0x3f, 0x96, - 0xdb, 0x71, - ]; - let nonce: [u8; 12] = [ - 0x11, 0x3b, 0x97, 0x85, 0x97, 0x18, 0x64, 0xc8, 0x3b, 0x01, 0xc7, 0x87, - ]; - let plaintext = []; - let _aad: &[u8] = &[]; - let expected_ciphertext: &[u8] = &[]; - let expected_tag: &[u8] = &[ - 0x72, 0xac, 0x84, 0x93, 0xe3, 0xa5, 0x22, 0x8b, 0x5d, 0x13, 0x0a, 0x69, 0xd2, 0x51, - 0x0e, 0x42, - ]; - let (c, tag) = encrypt::<4, 4, 10>(&key, &nonce, &plaintext); - assert_eq!(c, expected_ciphertext); + // test vector from line 7 + let key: [u8; 16] = hex_to_array("cf063a34d4a9a76c2c86787d3f96db71"); + let iv: [u8; 12] = hex_to_array("113b9785971864c83b01c787"); + let pt = []; + let expected_c: &[u8] = &[]; + let expected_tag: [u8; 16] = hex_to_array("72ac8493e3a5228b5d130a69d2510e42"); + let (c, tag) = encrypt::<4, 4, 10>(&key, &iv, &pt); + assert_eq!(c, expected_c); + assert_eq!(tag, expected_tag); + + // test vector from line 4417 + let key: [u8; 16] = hex_to_array("e98b72a9881a84ca6b76e0f43e68647a"); + let iv: [u8; 12] = hex_to_array("8b23299fde174053f3d652ba"); + let pt: [u8; 16] = hex_to_array("28286a321293253c3e0aa2704a278032"); + let expected_c: [u8; 16] = hex_to_array("5a3c1cf1985dbb8bed818036fdd5ab42"); + let expected_tag: [u8; 16] = hex_to_array("23c7ab0f952b7091cd324835043b5eb5"); + let (c, tag) = encrypt::<4, 4, 10>(&key, &iv, &pt); + assert_eq!(c, expected_c); + assert_eq!(tag, expected_tag); + + // test vector from line 8834 + let key: [u8; 16] = hex_to_array("387218b246c1a8257748b56980e50c94"); + let iv: [u8; 12] = hex_to_array("dd7e014198672be39f95b69d"); + let expected_c: [u8; 13] = hex_to_array("cdba9e73eaf3d38eceb2b04a8d"); + let expected_tag: [u8; 16] = hex_to_array("ecf90f4a47c9c626d6fb2c765d201556"); + let pt: [u8; 13] = hex_to_array("48f5b426baca03064554cc2b30"); + let (c, tag) = encrypt::<4, 4, 10>(&key, &iv, &pt); + assert_eq!(c, expected_c); assert_eq!(tag, expected_tag); - // test vector at line 4417 - let key: [u8; 16] = [ - 0xe9, 0x8b, 0x72, 0xa9, 0x88, 0x1a, 0x84, 0xca, 0x6b, 0x76, 0xe0, 0xf4, 0x3e, 0x68, - 0x64, 0x7a, - ]; - let nonce: [u8; 12] = [ - 0x8b, 0x23, 0x29, 0x9f, 0xde, 0x17, 0x40, 0x53, 0xf3, 0xd6, 0x52, 0xba, - ]; - let plaintext = [ - 0x28, 0x28, 0x6a, 0x32, 0x12, 0x93, 0x25, 0x3c, 0x3e, 0x0a, 0xa2, 0x70, 0x4a, 0x27, - 0x80, 0x32, - ]; - let _aad: &[u8] = &[]; - let expected_ciphertext: &[u8] = &[ - 0x5a, 0x3c, 0x1c, 0xf1, 0x98, 0x5d, 0xbb, 0x8b, 0xed, 0x81, 0x80, 0x36, 0xfd, 0xd5, - 0xab, 0x42, - ]; - let expected_tag: &[u8] = &[ - 0x23, 0xc7, 0xab, 0x0f, 0x95, 0x2b, 0x70, 0x91, 0xcd, 0x32, 0x48, 0x35, 0x04, 0x3b, - 0x5e, 0xb5, - ]; - let (c, tag) = encrypt::<4, 4, 10>(&key, &nonce, &plaintext); - assert_eq!(c, expected_ciphertext); + // test vector from line 13237 + let key: [u8; 16] = hex_to_array("bfd414a6212958a607a0f5d3ab48471d"); + let iv: [u8; 12] = hex_to_array("86d8ea0ab8e40dcc481cd0e2"); + let expected_c: [u8; 32] = + hex_to_array("62171db33193292d930bf6647347652c1ef33316d7feca99d54f1db4fcf513f8"); + let expected_tag: [u8; 16] = hex_to_array("c28280aa5c6c7a8bd366f28c1cfd1f6e"); + let pt: [u8; 32] = + hex_to_array("a6b76a066e63392c9443e60272ceaeb9d25c991b0f2e55e2804e168c05ea591a"); + let (c, tag) = encrypt::<4, 4, 10>(&key, &iv, &pt); + assert_eq!(c, expected_c); assert_eq!(tag, expected_tag); } /// test checking against external aes-gcm lib #[test] fn test_with_external_lib() -> anyhow::Result<()> { - let key: &[u8; 16] = &[42; 16]; + test_with_external_lib_op::<4, 4, 10, Aes128Gcm>()?; + test_with_external_lib_op::<8, 4, 14, Aes256Gcm>() + } + fn test_with_external_lib_op<const NK: usize, const NB: usize, const NR: usize, C>( + ) -> anyhow::Result<()> + where + [(); NK * NB]:, + [(); 4 * (NR + 1)]:, + C: KeyInit + Aead, + { + let key: &[u8; NK * NB] = &[42; NK * NB]; let nonce: &[u8; 12] = &[111; 12]; - let pt: &[u8] = &[42u8; 16]; + let pt: Vec<Vec<u8>> = vec![ + vec![42u8; 0], + vec![42u8; 16], + vec![42u8; 32], + vec![42u8; 1], + vec![42u8; 17], + b"test plaintext".to_vec(), + b"test plaintext for AES-GCM".to_vec(), + rand_bytes(131), + rand_bytes(1027), + rand_bytes(4242), + ]; - let key_ext: &Key<Aes128Gcm> = key.into(); let nonce_ext = Nonce::from_slice(nonce); - let cipher = Aes128Gcm::new(&key_ext); + let cipher = C::new_from_slice(key.as_slice()).unwrap(); - let ciphertext = cipher.encrypt(&nonce_ext, pt.as_ref()).unwrap(); + for pt_i in pt.iter() { + let ciphertext = cipher.encrypt(nonce_ext, pt_i.as_ref()).unwrap(); - let plaintext = cipher.decrypt(&nonce_ext, ciphertext.as_ref()).unwrap(); - assert_eq!(plaintext, pt); + let plaintext = cipher.decrypt(nonce_ext, ciphertext.as_ref()).unwrap(); + assert_eq!(plaintext, *pt_i); - let (c, t) = encrypt::<4, 4, 10>(key, nonce, &pt); - assert_eq!([c, t].concat(), ciphertext); + let (c, t) = encrypt::<NK, NB, NR>(key, nonce, pt_i); + assert_eq!(c.len() + t.len(), ciphertext.len()); + assert_eq!([c, t].concat(), ciphertext); + } Ok(()) + } + fn rand_bytes(n: usize) -> Vec<u8> { + let mut v = vec![0u8; n]; + rand::rng().fill_bytes(&mut v); + v } }