Skip to main content

crypto/aes/
aes256.rs

1/// Pure-Rust AES-256 block cipher and AES-256-GCM authenticated encryption.
2///
3/// Design notes:
4/// - Favours speed over constant-time execution (table-driven S-box lookups).
5/// - The x86-64 code path (aes256_amd64) dispatches to AES-NI + PCLMULQDQ
6///   at runtime and is used whenever the required CPU features are present.
7use crate::AeadError;
8
9// ── AES constants ─────────────────────────────────────────────────────────────
10
11#[rustfmt::skip]
12pub(crate) const SBOX: [u8; 256] = [
13    0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76,
14    0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0,
15    0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15,
16    0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75,
17    0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84,
18    0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf,
19    0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8,
20    0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2,
21    0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73,
22    0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb,
23    0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79,
24    0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08,
25    0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a,
26    0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e,
27    0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf,
28    0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16,
29];
30
31#[rustfmt::skip]
32const SBOX_INV: [u8; 256] = [
33    0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb,
34    0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb,
35    0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e,
36    0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25,
37    0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92,
38    0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84,
39    0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06,
40    0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b,
41    0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73,
42    0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e,
43    0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b,
44    0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4,
45    0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f,
46    0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef,
47    0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61,
48    0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d,
49];
50
51/// Round constants for AES key expansion (RCON[1..10]).
52const RCON: [u8; 11] = [0x00, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36];
53
54// ── AES-256 key schedule ───────────────────────────────────────────────────────
55
56/// AES-256 expanded key: 15 round keys × 16 bytes = 240 bytes.
57pub(crate) type RoundKeys = [[u8; 16]; 15];
58
59#[inline(always)]
60fn rot_word(w: [u8; 4]) -> [u8; 4] {
61    [w[1], w[2], w[3], w[0]]
62}
63
64#[inline(always)]
65fn sub_word(w: [u8; 4]) -> [u8; 4] {
66    [
67        SBOX[w[0] as usize],
68        SBOX[w[1] as usize],
69        SBOX[w[2] as usize],
70        SBOX[w[3] as usize],
71    ]
72}
73
74/// Expand a 256-bit key into 15 AES round keys (FIPS 197 §5.2, Nk=8, Nr=14).
75pub(crate) fn key_expand(key: &[u8; 32]) -> RoundKeys {
76    // W[0..60] – 60 words of 4 bytes each
77    let mut w = [[0u8; 4]; 60];
78
79    for i in 0..8 {
80        w[i] = [key[4 * i], key[4 * i + 1], key[4 * i + 2], key[4 * i + 3]];
81    }
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));
87            temp[0] ^= RCON[i / 8];
88        } else if i % 8 == 4 {
89            temp = sub_word(temp);
90        }
91        w[i] = [
92            w[i - 8][0] ^ temp[0],
93            w[i - 8][1] ^ temp[1],
94            w[i - 8][2] ^ temp[2],
95            w[i - 8][3] ^ temp[3],
96        ];
97    }
98
99    let mut rk = [[0u8; 16]; 15];
100    for i in 0..15 {
101        for j in 0..4 {
102            rk[i][4 * j..4 * j + 4].copy_from_slice(&w[4 * i + j]);
103        }
104    }
105    rk
106}
107
108// ── AES block cipher (encrypt / decrypt) ─────────────────────────────────────
109
110#[inline(always)]
111fn xtime(a: u8) -> u8 {
112    let hi = a & 0x80;
113    let b = a << 1;
114    if hi != 0 { b ^ 0x1b } else { b }
115}
116
117#[inline(always)]
118fn add_round_key(state: &mut [u8; 16], rk: &[u8; 16]) {
119    for i in 0..16 {
120        state[i] ^= rk[i];
121    }
122}
123
124#[inline(always)]
125fn sub_bytes(state: &mut [u8; 16]) {
126    for b in state.iter_mut() {
127        *b = SBOX[*b as usize];
128    }
129}
130
131/// AES ShiftRows: row r is shifted left by r positions (column-major layout).
132#[inline(always)]
133fn shift_rows(s: &mut [u8; 16]) {
134    let t = *s;
135    // row 0: no shift
136    // row 1: shift left 1
137    s[1] = t[5];
138    s[5] = t[9];
139    s[9] = t[13];
140    s[13] = t[1];
141    // row 2: shift left 2
142    s[2] = t[10];
143    s[6] = t[14];
144    s[10] = t[2];
145    s[14] = t[6];
146    // row 3: shift left 3
147    s[3] = t[15];
148    s[7] = t[3];
149    s[11] = t[7];
150    s[15] = t[11];
151}
152
153/// AES MixColumns on a single column (bytes at indices col*4 .. col*4+3).
154#[inline(always)]
155fn mix_col(s: &mut [u8; 16], col: usize) {
156    let i = col * 4;
157    let s0 = s[i];
158    let s1 = s[i + 1];
159    let s2 = s[i + 2];
160    let s3 = s[i + 3];
161    s[i] = xtime(s0) ^ xtime(s1) ^ s1 ^ s2 ^ s3;
162    s[i + 1] = s0 ^ xtime(s1) ^ xtime(s2) ^ s2 ^ s3;
163    s[i + 2] = s0 ^ s1 ^ xtime(s2) ^ xtime(s3) ^ s3;
164    s[i + 3] = xtime(s0) ^ s0 ^ s1 ^ s2 ^ xtime(s3);
165}
166
167#[inline(always)]
168fn mix_columns(state: &mut [u8; 16]) {
169    mix_col(state, 0);
170    mix_col(state, 1);
171    mix_col(state, 2);
172    mix_col(state, 3);
173}
174
175// ── Decryption helpers ────────────────────────────────────────────────────────
176
177#[inline(always)]
178fn inv_sub_bytes(state: &mut [u8; 16]) {
179    for b in state.iter_mut() {
180        *b = SBOX_INV[*b as usize];
181    }
182}
183
184#[inline(always)]
185fn inv_shift_rows(s: &mut [u8; 16]) {
186    let t = *s;
187    // row 1: shift right 1
188    s[1] = t[13];
189    s[5] = t[1];
190    s[9] = t[5];
191    s[13] = t[9];
192    // row 2: shift right 2
193    s[2] = t[10];
194    s[6] = t[14];
195    s[10] = t[2];
196    s[14] = t[6];
197    // row 3: shift right 3
198    s[3] = t[7];
199    s[7] = t[11];
200    s[11] = t[15];
201    s[15] = t[3];
202}
203
204/// Multiply in GF(2^8) mod 0x11b.
205#[inline(always)]
206fn gmul(mut a: u8, mut b: u8) -> u8 {
207    let mut p = 0u8;
208    for _ in 0..8 {
209        if b & 1 != 0 {
210            p ^= a;
211        }
212        let carry = a & 0x80;
213        a <<= 1;
214        if carry != 0 {
215            a ^= 0x1b;
216        }
217        b >>= 1;
218    }
219    p
220}
221
222#[inline(always)]
223fn inv_mix_col(s: &mut [u8; 16], col: usize) {
224    let i = col * 4;
225    let s0 = s[i];
226    let s1 = s[i + 1];
227    let s2 = s[i + 2];
228    let s3 = s[i + 3];
229    s[i] = gmul(0x0e, s0) ^ gmul(0x0b, s1) ^ gmul(0x0d, s2) ^ gmul(0x09, s3);
230    s[i + 1] = gmul(0x09, s0) ^ gmul(0x0e, s1) ^ gmul(0x0b, s2) ^ gmul(0x0d, s3);
231    s[i + 2] = gmul(0x0d, s0) ^ gmul(0x09, s1) ^ gmul(0x0e, s2) ^ gmul(0x0b, s3);
232    s[i + 3] = gmul(0x0b, s0) ^ gmul(0x0d, s1) ^ gmul(0x09, s2) ^ gmul(0x0e, s3);
233}
234
235#[inline(always)]
236fn inv_mix_columns(state: &mut [u8; 16]) {
237    inv_mix_col(state, 0);
238    inv_mix_col(state, 1);
239    inv_mix_col(state, 2);
240    inv_mix_col(state, 3);
241}
242
243// ── Pure-Rust AES-256 block encrypt / decrypt ─────────────────────────────────
244
245/// Encrypt one 16-byte block (pure Rust, FIPS 197 §5.1).
246pub(crate) fn encrypt_block(rk: &RoundKeys, block: &[u8; 16]) -> [u8; 16] {
247    let mut state = *block;
248    add_round_key(&mut state, &rk[0]);
249    for r in 1..14 {
250        sub_bytes(&mut state);
251        shift_rows(&mut state);
252        mix_columns(&mut state);
253        add_round_key(&mut state, &rk[r]);
254    }
255    sub_bytes(&mut state);
256    shift_rows(&mut state);
257    add_round_key(&mut state, &rk[14]);
258    state
259}
260
261/// Decrypt one 16-byte block (pure Rust, FIPS 197 §5.3).
262pub(crate) fn decrypt_block(rk: &RoundKeys, block: &[u8; 16]) -> [u8; 16] {
263    let mut state = *block;
264    add_round_key(&mut state, &rk[14]);
265    for r in (1..14).rev() {
266        inv_shift_rows(&mut state);
267        inv_sub_bytes(&mut state);
268        add_round_key(&mut state, &rk[r]);
269        inv_mix_columns(&mut state);
270    }
271    inv_shift_rows(&mut state);
272    inv_sub_bytes(&mut state);
273    add_round_key(&mut state, &rk[0]);
274    state
275}
276
277// ── GF(2^128) arithmetic for GHASH ───────────────────────────────────────────
278//
279// Representation: a 128-bit GCM element is stored as `u128` where
280//   – bit 127 (MSB of u128) = coefficient of x^0  (= MSB of the first byte)
281//   – bit 0   (LSB of u128) = coefficient of x^127 (= LSB of the last byte)
282//
283// This matches `u128::from_be_bytes(block)` exactly.
284//
285// Multiplication by x in this field:
286//   All coefficients shift up by one degree.  If the old x^127 coefficient
287//   (u128 bit 0) was 1, we reduce by x^128 ≡ x^7 + x^2 + x + 1, i.e.
288//   XOR with 0xe1 << 120.
289
290#[inline(always)]
291fn gf128_mul_x(v: u128) -> u128 {
292    let carry = v & 1; // coefficient of x^127 before shift
293    let shifted = v >> 1;
294    if carry != 0 {
295        shifted ^ (0xe1u128 << 120)
296    } else {
297        shifted
298    }
299}
300
301/// Multiply two GCM elements in GF(2^128).
302/// Both operands and the result use big-endian byte layout (NIST SP 800-38D).
303pub(crate) fn gf128_mul(x: &[u8; 16], h: &[u8; 16]) -> [u8; 16] {
304    let x_val = u128::from_be_bytes(*x);
305    let h_val = u128::from_be_bytes(*h);
306    let mut z = 0u128;
307    let mut v = h_val;
308
309    // Iterate over bits of X from MSB (bit 127 = x_0 = coeff of x^0) down
310    // to LSB (bit 0 = x_127 = coeff of x^127).
311    // v holds H * α^(127-k) at iteration k.
312    for k in (0..128u32).rev() {
313        if (x_val >> k) & 1 == 1 {
314            z ^= v;
315        }
316        v = gf128_mul_x(v);
317    }
318    z.to_be_bytes()
319}
320
321// ── GHASH ─────────────────────────────────────────────────────────────────────
322
323/// Update a running GHASH state by XOR-ing one 16-byte block and multiplying
324/// by H (NIST SP 800-38D §6.4).
325#[inline(always)]
326fn ghash_block(state: &mut [u8; 16], h: &[u8; 16], block: &[u8; 16]) {
327    for i in 0..16 {
328        state[i] ^= block[i];
329    }
330    let new = gf128_mul(state, h);
331    *state = new;
332}
333
334/// Feed an arbitrarily-sized byte slice into GHASH, zero-padding the last
335/// block if necessary.
336fn ghash_update(state: &mut [u8; 16], h: &[u8; 16], data: &[u8]) {
337    let mut chunks = data.chunks_exact(16);
338    for chunk in chunks.by_ref() {
339        let block: [u8; 16] = chunk.try_into().unwrap();
340        ghash_block(state, h, &block);
341    }
342    let rem = chunks.remainder();
343    if !rem.is_empty() {
344        let mut padded = [0u8; 16];
345        padded[..rem.len()].copy_from_slice(rem);
346        ghash_block(state, h, &padded);
347    }
348}
349
350// ── AES-256-GCM ───────────────────────────────────────────────────────────────
351
352/// Increment the 32-bit big-endian counter stored in bytes [12..16].
353#[inline(always)]
354fn ctr_inc(counter: &mut [u8; 16]) {
355    let c = u32::from_be_bytes(counter[12..16].try_into().unwrap());
356    counter[12..16].copy_from_slice(&c.wrapping_add(1).to_be_bytes());
357}
358
359/// XOR `in_out` with the AES-CTR keystream produced by the given counter,
360/// starting at the supplied counter value (incremented for each block).
361fn ctr_encrypt(rk: &RoundKeys, in_out: &mut [u8], counter: &mut [u8; 16]) {
362    let n = in_out.len();
363    let mut i = 0;
364    while i + 16 <= n {
365        let ks = encrypt_block(rk, counter);
366        for k in 0..16 {
367            in_out[i + k] ^= ks[k];
368        }
369        ctr_inc(counter);
370        i += 16;
371    }
372    if i < n {
373        let ks = encrypt_block(rk, counter);
374        for k in 0..n - i {
375            in_out[i + k] ^= ks[k];
376        }
377        ctr_inc(counter);
378    }
379}
380
381/// Compute the GCM authentication tag (pure Rust).
382///
383/// `h`   – GHASH subkey = AES_K(0^128)
384/// `ej0` – E(J0) used to mask the final GHASH output
385fn compute_tag(h: &[u8; 16], aad: &[u8], ciphertext: &[u8], ej0: &[u8; 16]) -> [u8; 16] {
386    let mut state = [0u8; 16];
387
388    ghash_update(&mut state, h, aad);
389    ghash_update(&mut state, h, ciphertext);
390
391    // Length block: len(A) || len(C) in bits as two 64-bit big-endian integers
392    let mut len_block = [0u8; 16];
393    len_block[..8].copy_from_slice(&((aad.len() as u64) * 8).to_be_bytes());
394    len_block[8..].copy_from_slice(&((ciphertext.len() as u64) * 8).to_be_bytes());
395    ghash_block(&mut state, h, &len_block);
396
397    // Tag = GHASH ^ E(J0)
398    for i in 0..16 {
399        state[i] ^= ej0[i];
400    }
401    state
402}
403
404// ── Public struct ─────────────────────────────────────────────────────────────
405
406/// AES-256-GCM authenticated cipher (pure-Rust implementation).
407///
408/// On x86-64 machines with AES-NI + PCLMULQDQ the methods automatically
409/// dispatch to the hardware-accelerated path (see `aes256_amd64`).
410pub struct Aes256Gcm {
411    pub(crate) key: [u8; 32],
412    pub(crate) round_keys: RoundKeys,
413}
414
415impl Aes256Gcm {
416    pub const KEY_SIZE: usize = 32;
417    pub const TAG_SIZE: usize = 16;
418    pub const NONCE_SIZE: usize = 12;
419    const MAX_GCM_LEN: usize = (u32::MAX as usize - 1) * 16;
420
421    /// Create a new `Aes256Gcm` instance from a 32-byte key.
422    pub fn new(key: &[u8; 32]) -> Self {
423        Aes256Gcm {
424            key: *key,
425            round_keys: key_expand(key),
426        }
427    }
428
429    /// Encrypt `in_out` in-place and return the 16-byte authentication tag.
430    ///
431    /// * `nonce` – 12-byte nonce (must be unique per (key, plaintext) pair)
432    /// * `aad`   – additional authenticated data (not encrypted)
433    #[inline]
434    #[allow(unreachable_code)]
435    pub fn encrypt_in_place(&self, in_out: &mut [u8], nonce: &[u8; 12], aad: &[u8]) -> [u8; 16] {
436        // we assume that AES instructions are always present on aarch64
437        #[cfg(target_arch = "aarch64")]
438        {
439            use crate::aes::aes256_arm64::encrypt_armv8;
440            unsafe { return encrypt_armv8(&self.key, in_out, nonce, aad) }
441        }
442
443        // runtime detection of CPU features for x86 and x86_64 when the "std" feature is enabled
444        #[cfg(feature = "std")]
445        {
446            #[cfg(target_arch = "x86_64")]
447            {
448                use crate::aes::aes256_amd64::encrypt_aesni;
449                if std::arch::is_x86_feature_detected!("aes")
450                    && std::arch::is_x86_feature_detected!("pclmulqdq")
451                    && std::arch::is_x86_feature_detected!("ssse3")
452                    && std::arch::is_x86_feature_detected!("sse4.1")
453                {
454                    unsafe { return encrypt_aesni(&self.key, in_out, nonce, aad) }
455                }
456            }
457        }
458
459        #[cfg(not(feature = "std"))]
460        {
461            #[cfg(all(
462                target_feature = "aes",
463                target_feature = "pclmulqdq",
464                target_feature = "ssse3",
465                target_feature = "sse4.1"
466            ))]
467            {
468                use crate::aes::aes256_amd64::encrypt_aesni;
469                unsafe {
470                    return encrypt_aesni(&self.key, in_out, nonce, aad);
471                }
472            }
473        }
474
475        self.encrypt_in_place_soft(in_out, nonce, aad)
476    }
477
478    /// Decrypt `in_out` in-place and verify the authentication tag.
479    ///
480    /// Returns `Err(Error::Unspecified)` if the tag does not match.
481    #[inline]
482    #[allow(unreachable_code)]
483    pub fn decrypt_in_place(
484        &self,
485        in_out: &mut [u8],
486        tag: &[u8; 16],
487        nonce: &[u8; 12],
488        aad: &[u8],
489    ) -> Result<(), AeadError> {
490        // we assume that AES instructions are always present on aarch64
491        #[cfg(target_arch = "aarch64")]
492        {
493            use crate::aes::aes256_arm64::decrypt_armv8;
494            unsafe { return decrypt_armv8(&self.key, in_out, tag, nonce, aad) }
495        }
496
497        #[cfg(feature = "std")]
498        {
499            #[cfg(target_arch = "x86_64")]
500            {
501                use crate::aes::aes256_amd64::decrypt_aesni;
502                if std::arch::is_x86_feature_detected!("aes")
503                    && std::arch::is_x86_feature_detected!("pclmulqdq")
504                    && std::arch::is_x86_feature_detected!("ssse3")
505                    && std::arch::is_x86_feature_detected!("sse4.1")
506                {
507                    unsafe { return decrypt_aesni(&self.key, in_out, tag, nonce, aad) }
508                }
509            }
510        }
511
512        #[cfg(not(feature = "std"))]
513        {
514            #[cfg(all(
515                target_feature = "aes",
516                target_feature = "pclmulqdq",
517                target_feature = "ssse3",
518                target_feature = "sse4.1"
519            ))]
520            {
521                use crate::aes::aes256_amd64::decrypt_aesni;
522                unsafe {
523                    return decrypt_aesni(&self.key, in_out, tag, nonce, aad);
524                }
525            }
526        }
527
528        self.decrypt_in_place_soft(in_out, tag, nonce, aad)
529    }
530
531    /// Pure-Rust encrypt implementation.
532    pub(crate) fn encrypt_in_place_soft(&self, in_out: &mut [u8], nonce: &[u8; 12], aad: &[u8]) -> [u8; 16] {
533        assert!(
534            in_out.len() <= Self::MAX_GCM_LEN,
535            "GCM plaintext exceeds maximum allowed length (2^32 - 2 blocks)"
536        );
537        let rk = &self.round_keys;
538        let h = encrypt_block(rk, &[0u8; 16]);
539
540        // J0 = nonce || 0x00000001
541        let mut j0 = [0u8; 16];
542        j0[..12].copy_from_slice(nonce);
543        j0[15] = 1;
544
545        let ej0 = encrypt_block(rk, &j0);
546
547        // CTR starts at J0 + 1 (= nonce || 0x00000002)
548        let mut ctr = j0;
549        ctr_inc(&mut ctr);
550
551        ctr_encrypt(rk, in_out, &mut ctr);
552        compute_tag(&h, aad, in_out, &ej0)
553    }
554
555    /// Pure-Rust decrypt implementation.
556    pub(crate) fn decrypt_in_place_soft(
557        &self,
558        in_out: &mut [u8],
559        tag: &[u8; 16],
560        nonce: &[u8; 12],
561        aad: &[u8],
562    ) -> Result<(), AeadError> {
563        if in_out.len() > Self::MAX_GCM_LEN {
564            return Err(AeadError::InvalidCiphertext);
565        }
566        let rk = &self.round_keys;
567        let h = encrypt_block(rk, &[0u8; 16]);
568
569        let mut j0 = [0u8; 16];
570        j0[..12].copy_from_slice(nonce);
571        j0[15] = 1;
572
573        let ej0 = encrypt_block(rk, &j0);
574
575        // Verify tag before decrypting (authenticate-then-decrypt ordering)
576        let expected_tag = compute_tag(&h, aad, in_out, &ej0);
577
578        // Constant-time comparison to avoid timing oracle
579        let mut diff = 0u8;
580        for i in 0..16 {
581            diff |= expected_tag[i] ^ tag[i];
582        }
583        if diff != 0 {
584            return Err(AeadError::InvalidCiphertext);
585        }
586
587        let mut ctr = j0;
588        ctr_inc(&mut ctr);
589        ctr_encrypt(rk, in_out, &mut ctr);
590
591        Ok(())
592    }
593}
594
595#[cfg(test)]
596mod tests {
597    use super::*;
598
599    // TODO: Add tests:
600    // https://www.tuhs.org/cgi-bin/utree.pl?file=OpenBSD-4.6/regress/sys/crypto/aes/vectors/ecbnk48.txt
601    // https://android.googlesource.com/platform/libcore/+/1db6bf619611525020518a180f0ee82c8cd50af2/luni/src/test/resources/crypto/aes-cbc.csv
602
603    fn hb<const N: usize>(s: &str) -> [u8; N] {
604        let v = hex::decode(s).unwrap();
605        assert_eq!(v.len(), N, "wrong hex length for '{s}'");
606        v.try_into().unwrap()
607    }
608
609    // ── AES-256 block cipher (FIPS 197 Appendix B + C) ────────────────────────
610
611    /// NIST FIPS 197 Appendix B – AES-128 vectors (re-confirmed in AES-256 test)
612    /// These come from FIPS 197 Appendix C.3 (AES-256).
613    #[test]
614    fn fips197_aes256_encrypt() {
615        // FIPS 197 Appendix C.3
616        let key: [u8; 32] = hb("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f");
617        let pt: [u8; 16] = hb("00112233445566778899aabbccddeeff");
618        let ct_expected: [u8; 16] = hb("8ea2b7ca516745bfeafc49904b496089");
619
620        let rk = key_expand(&key);
621        let ct = encrypt_block(&rk, &pt);
622        assert_eq!(ct, ct_expected);
623    }
624
625    #[test]
626    fn fips197_aes256_decrypt() {
627        let key: [u8; 32] = hb("000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f");
628        let ct: [u8; 16] = hb("8ea2b7ca516745bfeafc49904b496089");
629        let pt_expected: [u8; 16] = hb("00112233445566778899aabbccddeeff");
630
631        let rk = key_expand(&key);
632        let pt = decrypt_block(&rk, &ct);
633        assert_eq!(pt, pt_expected);
634    }
635
636    /// NIST SP 800-38A ECB-AES256 vectors (F.1.5 / F.1.6).
637    #[test]
638    fn nist_sp800_38a_aes256_ecb() {
639        let key: [u8; 32] = hb("603deb1015ca71be2b73aef0857d77811f352c073b6108d72d9810a30914dff4");
640
641        let blocks: &[([u8; 16], [u8; 16])] = &[
642            (hb("6bc1bee22e409f96e93d7e117393172a"), hb("f3eed1bdb5d2a03c064b5a7e3db181f8")),
643            (hb("ae2d8a571e03ac9c9eb76fac45af8e51"), hb("591ccb10d410ed26dc5ba74a31362870")),
644            (hb("30c81c46a35ce411e5fbc1191a0a52ef"), hb("b6ed21b99ca6f4f9f153e7b1beafed1d")),
645            (hb("f69f2445df4f9b17ad2b417be66c3710"), hb("23304b7a39f9f3ff067d8d8f9e24ecc7")),
646        ];
647
648        let rk = key_expand(&key);
649        for (pt, ct) in blocks {
650            assert_eq!(encrypt_block(&rk, pt), *ct);
651            assert_eq!(decrypt_block(&rk, ct), *pt);
652        }
653    }
654
655    /// NIST Known Answer Test (KAT) – a few AES-256 single-block KATs.
656    #[test]
657    fn aes256_kat_vectors() {
658        // key, plaintext, ciphertext
659        let vectors: &[([u8; 32], [u8; 16], [u8; 16])] = &[
660            // All-zero key and plaintext
661            ([0u8; 32], [0u8; 16], hb("dc95c078a2408989ad48a21492842087")),
662            // Key = 0x01..0x20, PT = 0
663            (
664                hb("0101010101010101010101010101010101010101010101010101010101010101"),
665                [0u8; 16],
666                hb("7298caa565031eadc6ce23d23ea66378"),
667            ),
668            // Key = 0xff..0xff
669            ([0xff; 32], [0u8; 16], hb("4bf85f1b5d54adbc307b0a048389adcb")),
670        ];
671
672        for (key, pt, ct_expected) in vectors {
673            let rk = key_expand(key);
674            let ct = encrypt_block(&rk, pt);
675            assert_eq!(ct, *ct_expected, "key={}", hex::encode(key));
676            let pt2 = decrypt_block(&rk, &ct);
677            assert_eq!(pt2, *pt, "round-trip failed");
678        }
679    }
680
681    #[test]
682    fn encrypt_decrypt_roundtrip_random() {
683        let key: [u8; 32] = hb("deadbeefcafebabedeadbeefcafebabe0011223344556677deadbeefcafebabe");
684        let rk = key_expand(&key);
685        for seed in 0u8..=255 {
686            let pt = [seed; 16];
687            let ct = encrypt_block(&rk, &pt);
688            let pt2 = decrypt_block(&rk, &ct);
689            assert_eq!(pt2, pt);
690        }
691    }
692
693    // ── GF(2^128) multiplication ───────────────────────────────────────────────
694
695    #[test]
696    fn gf128_mul_zero() {
697        let h = [
698            0x66, 0xe9, 0x4b, 0xd4, 0xef, 0x8a, 0x2c, 0x3b, 0x88, 0x4c, 0xfa, 0x59, 0xca, 0x34, 0x2b, 0x2e,
699        ];
700        let zero = [0u8; 16];
701        assert_eq!(gf128_mul(&zero, &h), zero);
702        assert_eq!(gf128_mul(&h, &zero), zero);
703    }
704
705    #[test]
706    fn gf128_mul_commutativity() {
707        let a: [u8; 16] = hb("66e94bd4ef8a2c3b884cfa59ca342b2e");
708        let b: [u8; 16] = hb("feedfacedeadbeeffeedfacedeadbeef");
709        assert_eq!(gf128_mul(&a, &b), gf128_mul(&b, &a));
710    }
711
712    // H and X from GCM Test Case 2 (NIST SP 800-38D Appendix B).
713    #[test]
714    fn gf128_mul_nist_tv2() {
715        // H = AES_K(0) for K = all-zeros 128-bit key → irrelevant for 256-bit here,
716        // but we test the raw GF multiplication with known values from NIST test vectors.
717        // From TC2: H = 66e94bd4ef8a2c3b884cfa59ca342b2e
718        //           X (first GHASH input) = feedfacedeadbeeffeedfacedeadbeef
719        // Expected product from the NIST spec reference implementation.
720        let h: [u8; 16] = hb("66e94bd4ef8a2c3b884cfa59ca342b2e");
721        let x: [u8; 16] = hb("feedfacedeadbeeffeedfacedeadbeef");
722        // Computed offline with a reference implementation.
723        let expected: [u8; 16] = hb("88eddca9968dec8b9c952d6ae0290a82");
724        assert_eq!(gf128_mul(&x, &h), expected);
725    }
726
727    // ── AES-256-GCM (NIST SP 800-38D Appendix B and additional vectors) ────────
728
729    struct GcmVector {
730        key: &'static str,
731        nonce: &'static str,
732        pt: &'static str,
733        aad: &'static str,
734        ct: &'static str,
735        tag: &'static str,
736    }
737
738    const NIST_GCM_VECTORS: &[GcmVector] = &[
739        // Test Case 13 – empty plaintext, empty AAD, 256-bit key
740        GcmVector {
741            key: "0000000000000000000000000000000000000000000000000000000000000000",
742            nonce: "000000000000000000000000",
743            pt: "",
744            aad: "",
745            ct: "",
746            tag: "530f8afbc74536b9a963b4f1c4cb738b",
747        },
748        // Test Case 14 – plaintext = 16 zero bytes, empty AAD, 256-bit key
749        GcmVector {
750            key: "0000000000000000000000000000000000000000000000000000000000000000",
751            nonce: "000000000000000000000000",
752            pt: "00000000000000000000000000000000",
753            aad: "",
754            ct: "cea7403d4d606b6e074ec5d3baf39d18",
755            tag: "d0d1c8a799996bf0265b98b5d48ab919",
756        },
757        // Test Case 15 – from NIST SP 800-38D
758        GcmVector {
759            key: "feffe9928665731c6d6a8f9467308308feffe9928665731c6d6a8f9467308308",
760            nonce: "cafebabefacedbaddecaf888",
761            pt: "d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b391aafd255",
762            aad: "",
763            ct: "522dc1f099567d07f47f37a32a84427d643a8cdcbfe5c0c97598a2bd2555d1aa8cb08e48590dbb3da7b08b1056828838c5f61e6393ba7a0abcc9f662898015ad",
764            tag: "b094dac5d93471bdec1a502270e3cc6c",
765        },
766        // Test Case 16 – with AAD
767        GcmVector {
768            key: "feffe9928665731c6d6a8f9467308308feffe9928665731c6d6a8f9467308308",
769            nonce: "cafebabefacedbaddecaf888",
770            pt: "d9313225f88406e5a55909c5aff5269a86a7a9531534f7da2e4c303d8a318a721c3c0c95956809532fcf0e2449a6b525b16aedf5aa0de657ba637b39",
771            aad: "feedfacedeadbeeffeedfacedeadbeefabaddad2",
772            ct: "522dc1f099567d07f47f37a32a84427d643a8cdcbfe5c0c97598a2bd2555d1aa8cb08e48590dbb3da7b08b1056828838c5f61e6393ba7a0abcc9f662",
773            tag: "76fc6ece0f4e1768cddf8853bb2d551b",
774        },
775    ];
776
777    include!("aes256_gcm_vectors.rs");
778
779    fn run_gcm_vector_soft(v: &GcmVector) {
780        let key: [u8; 32] = hb(v.key);
781        let nonce: [u8; 12] = hb(v.nonce);
782        let pt = hex::decode(v.pt).unwrap();
783        let aad = hex::decode(v.aad).unwrap();
784        let expected_ct = hex::decode(v.ct).unwrap();
785        let expected_tag: [u8; 16] = hb(v.tag);
786
787        let cipher = Aes256Gcm::new(&key);
788
789        // Encrypt
790        let mut buf = pt.clone();
791        let tag = cipher.encrypt_in_place_soft(&mut buf, &nonce, &aad);
792        assert_eq!(buf, expected_ct, "ciphertext mismatch for key={}", v.key);
793        assert_eq!(tag, expected_tag, "tag mismatch for key={}", v.key);
794
795        // Decrypt
796        let mut buf2 = expected_ct.clone();
797        cipher
798            .decrypt_in_place_soft(&mut buf2, &expected_tag, &nonce, &aad)
799            .expect("decrypt failed");
800        assert_eq!(buf2, pt, "plaintext mismatch after decrypt for key={}", v.key);
801    }
802
803    #[test]
804    fn nist_gcm_test_vectors_soft() {
805        for v in NIST_GCM_VECTORS.iter().chain(EXTRA_GCM_VECTORS.iter()) {
806            run_gcm_vector_soft(v);
807        }
808    }
809
810    #[test]
811    fn gcm_tag_mismatch_returns_error_soft() {
812        let key = [0u8; 32];
813        let nonce = [0u8; 12];
814        let cipher = Aes256Gcm::new(&key);
815        let mut buf = b"hello world".to_vec();
816        let tag = cipher.encrypt_in_place_soft(&mut buf, &nonce, &[]);
817        // Flip one tag byte
818        let mut bad_tag = tag;
819        bad_tag[0] ^= 0xff;
820        let mut buf2 = buf.clone();
821        assert!(cipher.decrypt_in_place_soft(&mut buf2, &bad_tag, &nonce, &[]).is_err());
822    }
823
824    #[test]
825    fn gcm_encrypt_decrypt_large_soft() {
826        let key = [0xabu8; 32];
827        let nonce = [0x01u8; 12];
828        let aad = b"additional data";
829        let plaintext: Vec<u8> = (0u8..=255u8).cycle().take(1024).collect();
830
831        let cipher = Aes256Gcm::new(&key);
832        let mut buf = plaintext.clone();
833        let tag = cipher.encrypt_in_place_soft(&mut buf, &nonce, aad);
834        cipher
835            .decrypt_in_place_soft(&mut buf, &tag, &nonce, aad)
836            .expect("decrypt failed");
837        assert_eq!(buf, plaintext);
838    }
839
840    #[test]
841    fn gcm_empty_plaintext_nonempty_aad_soft() {
842        let key: [u8; 32] = hb("feffe9928665731c6d6a8f9467308308feffe9928665731c6d6a8f9467308308");
843        let nonce: [u8; 12] = hb("cafebabefacedbaddecaf888");
844        let aad = hex::decode("feedfacedeadbeeffeedfacedeadbeef").unwrap();
845        let cipher = Aes256Gcm::new(&key);
846        let mut buf: Vec<u8> = vec![];
847        let tag = cipher.encrypt_in_place_soft(&mut buf, &nonce, &aad);
848        cipher
849            .decrypt_in_place_soft(&mut buf, &tag, &nonce, &aad)
850            .expect("decrypt failed");
851    }
852
853    // ── Dispatching wrappers (use hardware path when available) ───────────────
854
855    #[test]
856    fn nist_gcm_test_vectors_dispatch() {
857        for v in NIST_GCM_VECTORS.iter().chain(EXTRA_GCM_VECTORS.iter()) {
858            let key: [u8; 32] = hb(v.key);
859            let nonce: [u8; 12] = hb(v.nonce);
860            let pt = hex::decode(v.pt).unwrap();
861            let aad = hex::decode(v.aad).unwrap();
862            let expected_ct = hex::decode(v.ct).unwrap();
863            let expected_tag: [u8; 16] = hb(v.tag);
864
865            let cipher = Aes256Gcm::new(&key);
866
867            let mut buf = pt.clone();
868            let tag = cipher.encrypt_in_place(&mut buf, &nonce, &aad);
869            assert_eq!(buf, expected_ct, "dispatch ciphertext mismatch key={}", v.key);
870            assert_eq!(tag, expected_tag, "dispatch tag mismatch key={}", v.key);
871
872            let mut buf2 = expected_ct.clone();
873            cipher
874                .decrypt_in_place(&mut buf2, &expected_tag, &nonce, &aad)
875                .expect("dispatch decrypt failed");
876            assert_eq!(buf2, pt);
877        }
878    }
879
880    // --- Wycheproof test vectors ---
881
882    #[test]
883    fn wycheproof_gcm_vectors() {
884        let data: serde_json::Value =
885            serde_json::from_str(include_str!("../../testdata/wycheproof/testvectors_v1/aes_gcm_test.json")).unwrap();
886        let mut valid_tested = 0u64;
887        let mut invalid_tested = 0u64;
888        for group in data["testGroups"].as_array().unwrap() {
889            if group["keySize"].as_u64() != Some(256) {
890                continue;
891            }
892            if group["ivSize"].as_u64() != Some(96) {
893                continue;
894            }
895            if group["tagSize"].as_u64() != Some(128) {
896                continue;
897            }
898            for test in group["tests"].as_array().unwrap() {
899                let key_hex = test["key"].as_str().unwrap();
900                let iv_hex = test["iv"].as_str().unwrap();
901                let msg_hex = test["msg"].as_str().unwrap();
902                let aad_hex = test["aad"].as_str().unwrap();
903                let ct_hex = test["ct"].as_str().unwrap();
904                let tag_hex = test["tag"].as_str().unwrap();
905                let result = test["result"].as_str().unwrap();
906
907                let key = hb::<32>(key_hex);
908                let nonce = hb::<12>(iv_hex);
909                let expected_ct = hex::decode(ct_hex).unwrap();
910                let expected_tag = hb::<16>(tag_hex);
911                let pt = hex::decode(msg_hex).unwrap();
912                let aad = hex::decode(aad_hex).unwrap();
913
914                let cipher = Aes256Gcm::new(&key);
915
916                if result == "valid" {
917                    let mut buf = pt.clone();
918                    let tag = cipher.encrypt_in_place(&mut buf, &nonce, &aad);
919                    assert_eq!(buf, expected_ct, "wycheproof GCM tcId={} ct mismatch", test["tcId"]);
920                    assert_eq!(tag, expected_tag, "wycheproof GCM tcId={} tag mismatch", test["tcId"]);
921
922                    let mut buf2 = expected_ct.clone();
923                    cipher
924                        .decrypt_in_place(&mut buf2, &expected_tag, &nonce, &aad)
925                        .expect("wycheproof GCM decrypt failed");
926                    assert_eq!(buf2, pt, "wycheproof GCM tcId={} pt mismatch", test["tcId"]);
927                    valid_tested += 1;
928                } else {
929                    let mut buf = expected_ct.clone();
930                    let result = cipher.decrypt_in_place(&mut buf, &expected_tag, &nonce, &aad);
931                    assert!(
932                        result.is_err(),
933                        "wycheproof GCM tcId={} expected invalid but passed",
934                        test["tcId"]
935                    );
936                    invalid_tested += 1;
937                }
938            }
939        }
940        assert!(valid_tested > 0, "no valid AES-GCM wycheproof tests were run");
941        assert!(invalid_tested > 0, "no invalid AES-GCM wycheproof tests were run");
942    }
943}