Signet Forge 0.1.0
C++20 Parquet library with AI-native extensions
DEMO
Loading...
Searching...
No Matches
aes_core.hpp
Go to the documentation of this file.
1// SPDX-License-Identifier: AGPL-3.0-or-later
2// Copyright 2026 Johnson Ogundeji
3#pragma once
4
7
8// ---------------------------------------------------------------------------
9// aes_core.hpp -- Bundled, zero-dependency, header-only AES-256 block cipher
10//
11// Implements the AES-256 block cipher as specified in FIPS-197:
12// https://csrc.nist.gov/publications/detail/fips/197/final
13//
14// This is a clean-room, table-based implementation providing correct AES-256
15// encrypt and decrypt for a single 128-bit block. Higher-level modes (GCM,
16// CTR) are built on top in separate headers.
17//
18// AES-256 parameters:
19// Key size: 32 bytes (256 bits)
20// Block size: 16 bytes (128 bits)
21// Rounds: 14
22// Round keys: 15 (initial + 14 rounds) = 240 bytes total
23//
24// Design decision (Gap C-14, NIST SP 800-131A):
25// Only AES-256 is supported. AES-128 and AES-192 are intentionally excluded.
26// Rationale:
27// 1. NIST SP 800-131A Rev.2 §4 recommends AES-256 for long-term security.
28// 2. Post-quantum threat models (Grover's algorithm) halve effective key
29// strength — AES-256 retains 128-bit security, AES-128 drops to 64-bit.
30// 3. Single key size eliminates key-length confusion bugs (CWE-326).
31// 4. Parquet Modular Encryption (PME) interop: both AES-128 and AES-256 are
32// valid per the PME spec, but AES-256 is the safe default. A future
33// AES-128 code path (Gap P-7) may be added for interop if needed.
34//
35// FIPS-197 test vector (Appendix C.3 -- AES-256):
36// Key: 000102030405060708090a0b0c0d0e0f
37// 101112131415161718191a1b1c1d1e1f
38// Plaintext: 00112233445566778899aabbccddeeff
39// Ciphertext:8ea2b7ca516745bfeafc49904b496089
40// ---------------------------------------------------------------------------
41
42#include <cstddef>
43#include <cstdint>
44#include <cstring>
45
46// AES-NI / ARMv8-CE hardware acceleration detection (Gap C-5)
47// Currently: T-table software implementation with S-box prefetch (CWE-208)
48// Future: Hardware AES instructions eliminate cache-timing side channels entirely
49#if defined(__AES__) || defined(_M_X64) || defined(_M_IX86)
50# if defined(__GNUC__) || defined(__clang__)
51# include <cpuid.h>
52# define SIGNET_HAS_AESNI_DETECT 1
53# elif defined(_MSC_VER)
54# include <intrin.h>
55# define SIGNET_HAS_AESNI_DETECT 1
56# endif
57#endif
58
59#if defined(__ARM_FEATURE_CRYPTO) || defined(__ARM_FEATURE_AES)
60# define SIGNET_HAS_ARM_AES 1
61#endif
62
64
65// ===========================================================================
66// AES S-box and inverse S-box (FIPS-197 Section 5.1.1)
67// ===========================================================================
68namespace detail::aes {
69
71static constexpr uint8_t SBOX[256] = {
72 0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5,
73 0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76,
74 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0,
75 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0,
76 0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc,
77 0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15,
78 0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a,
79 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75,
80 0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0,
81 0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84,
82 0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b,
83 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf,
84 0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85,
85 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8,
86 0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5,
87 0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2,
88 0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17,
89 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73,
90 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88,
91 0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb,
92 0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c,
93 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79,
94 0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9,
95 0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08,
96 0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6,
97 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a,
98 0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e,
99 0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e,
100 0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94,
101 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf,
102 0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68,
103 0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16
104};
105
107static constexpr uint8_t INV_SBOX[256] = {
108 0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38,
109 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb,
110 0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87,
111 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb,
112 0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d,
113 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e,
114 0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2,
115 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25,
116 0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16,
117 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92,
118 0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda,
119 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84,
120 0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a,
121 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06,
122 0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02,
123 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b,
124 0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea,
125 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73,
126 0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85,
127 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e,
128 0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89,
129 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b,
130 0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20,
131 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4,
132 0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31,
133 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f,
134 0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d,
135 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef,
136 0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0,
137 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61,
138 0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26,
139 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d
140};
141
146static constexpr uint8_t RCON[10] = {
147 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36
148};
149
150// ===========================================================================
151// GF(2^8) arithmetic for MixColumns (FIPS-197 Section 4.2)
152//
153// The irreducible polynomial is: x^8 + x^4 + x^3 + x + 1 (0x11b)
154// ===========================================================================
155
158inline constexpr uint8_t xtime(uint8_t a) {
159 return static_cast<uint8_t>((a << 1) ^ ((a & 0x80) ? 0x1b : 0x00));
160}
161
165inline constexpr uint8_t gf_mul(uint8_t a, uint8_t b) {
166 uint8_t result = 0;
167 for (int i = 0; i < 8; ++i) {
168 result ^= a & (static_cast<uint8_t>(0) - (b & 1));
169 uint8_t hi = a >> 7;
170 a = static_cast<uint8_t>(a << 1) ^ (0x1b & (static_cast<uint8_t>(0) - hi));
171 b >>= 1;
172 }
173 return result;
174}
175
176// ===========================================================================
177// SubWord / RotWord helpers for key expansion
178// ===========================================================================
179
181inline void sub_word(uint8_t word[4]) {
182 word[0] = SBOX[word[0]];
183 word[1] = SBOX[word[1]];
184 word[2] = SBOX[word[2]];
185 word[3] = SBOX[word[3]];
186}
187
189inline void rot_word(uint8_t word[4]) {
190 uint8_t tmp = word[0];
191 word[0] = word[1];
192 word[1] = word[2];
193 word[2] = word[3];
194 word[3] = tmp;
195}
196
203inline void secure_zero(void* ptr, size_t len) {
204 if (len == 0) return;
205 volatile unsigned char* p = static_cast<volatile unsigned char*>(ptr);
206 for (size_t i = 0; i < len; ++i) p[i] = 0;
207#if defined(__GNUC__) || defined(__clang__)
208 __asm__ __volatile__("" ::: "memory");
209#elif defined(_MSC_VER)
210 _ReadWriteBarrier();
211#endif
212}
213
217inline bool has_hardware_aes() noexcept {
218#if defined(SIGNET_HAS_AESNI_DETECT)
219 // x86: Check CPUID leaf 1, ECX bit 25 (AES-NI)
220 #if defined(__GNUC__) || defined(__clang__)
221 unsigned int eax = 0, ebx = 0, ecx = 0, edx = 0;
222 if (__get_cpuid(1, &eax, &ebx, &ecx, &edx)) {
223 return (ecx >> 25) & 1;
224 }
225 #elif defined(_MSC_VER)
226 int cpuInfo[4];
227 __cpuid(cpuInfo, 1);
228 return (cpuInfo[2] >> 25) & 1;
229 #endif
230 return false;
231#elif defined(SIGNET_HAS_ARM_AES)
232 return true; // ARM AES feature detected at compile time
233#else
234 return false;
235#endif
236}
237
238} // namespace detail::aes
239
240// ===========================================================================
241// Aes256 -- AES-256 block cipher (FIPS-197)
242// ===========================================================================
243
253class Aes256 {
254public:
255 static constexpr size_t KEY_SIZE = 32;
256 static constexpr size_t BLOCK_SIZE = 16;
257 static constexpr int NUM_ROUNDS = 14;
258
260 explicit Aes256(const uint8_t key[KEY_SIZE]) {
261 key_expansion(key);
262 }
263
264 Aes256(const Aes256&) = delete;
265 Aes256& operator=(const Aes256&) = delete;
266 Aes256(Aes256&& other) noexcept { std::memcpy(round_keys_, other.round_keys_, sizeof(round_keys_)); detail::aes::secure_zero(other.round_keys_, sizeof(other.round_keys_)); }
267 Aes256& operator=(Aes256&& other) noexcept { if (this != &other) { detail::aes::secure_zero(round_keys_, sizeof(round_keys_)); std::memcpy(round_keys_, other.round_keys_, sizeof(round_keys_)); detail::aes::secure_zero(other.round_keys_, sizeof(other.round_keys_)); } return *this; }
268
271 detail::aes::secure_zero(round_keys_, sizeof(round_keys_));
272 }
273
280 void encrypt_block(uint8_t block[BLOCK_SIZE]) const {
281 // Prefetch entire S-box into cache to mitigate timing side-channels.
282 // Ref: D.J. Bernstein "Cache-timing attacks on AES" (2005), CWE-208.
283 // TODO(C-5): When SIGNET_ENABLE_AESNI=ON and has_hardware_aes(),
284 // skip T-table prefetch and use _mm_aesenc_si128 intrinsics instead.
285 // This eliminates the cache-timing side channel entirely.
286 //
287 // ACCEPTED RISK (H-1): The S-box table lookup is fundamentally
288 // timing-vulnerable on CPUs without AES-NI/ARMv8-CE. The prefetch
289 // above reduces but does not eliminate the attack surface — an
290 // attacker sharing an L1 cache line can still observe access patterns
291 // via FLUSH+RELOAD or PRIME+PROBE. This is accepted until a hardware
292 // AES path (AES-NI _mm_aesenc_si128 / ARMv8-CE vaeseq_u8) is
293 // implemented in TODO(C-5). All deployments on shared hardware should
294 // prefer CPUs with hardware AES support.
295 for (size_t i = 0; i < 256; i += 64 / sizeof(uint8_t)) {
296 volatile uint8_t sink = detail::aes::SBOX[i];
297 (void)sink;
298 }
299
300 // State is organized as a 4x4 column-major matrix:
301 // state[row][col] = block[row + 4*col]
302 // We operate directly on the flat block array using index arithmetic.
303
304 // Round 0: AddRoundKey
305 add_round_key(block, 0);
306
307 // Rounds 1..13: SubBytes, ShiftRows, MixColumns, AddRoundKey
308 for (int round = 1; round < NUM_ROUNDS; ++round) {
309 sub_bytes(block);
310 shift_rows(block);
311 mix_columns(block);
312 add_round_key(block, round);
313 }
314
315 // Final round (14): SubBytes, ShiftRows, AddRoundKey (no MixColumns)
316 sub_bytes(block);
317 shift_rows(block);
318 add_round_key(block, NUM_ROUNDS);
319 }
320
328 void decrypt_block(uint8_t block[BLOCK_SIZE]) const {
329 // Prefetch entire inverse S-box into cache to mitigate timing side-channels.
330 // Ref: D.J. Bernstein "Cache-timing attacks on AES" (2005), CWE-208.
331 // ACCEPTED RISK (H-1): See encrypt_block() comment — same cache-timing
332 // residual risk applies to the inverse S-box lookup path.
333 for (size_t i = 0; i < 256; i += 64 / sizeof(uint8_t)) {
334 volatile uint8_t sink = detail::aes::INV_SBOX[i];
335 (void)sink;
336 }
337
338 // Round 14: AddRoundKey
339 add_round_key(block, NUM_ROUNDS);
340
341 // Rounds 13..1: InvShiftRows, InvSubBytes, AddRoundKey, InvMixColumns
342 for (int round = NUM_ROUNDS - 1; round >= 1; --round) {
343 inv_shift_rows(block);
344 inv_sub_bytes(block);
345 add_round_key(block, round);
346 inv_mix_columns(block);
347 }
348
349 // Final round (0): InvShiftRows, InvSubBytes, AddRoundKey
350 inv_shift_rows(block);
351 inv_sub_bytes(block);
352 add_round_key(block, 0);
353 }
354
355private:
356 // 15 round keys * 16 bytes each = 240 bytes
357 uint8_t round_keys_[BLOCK_SIZE * (NUM_ROUNDS + 1)]; // 240 bytes
358
359 // -----------------------------------------------------------------------
360 // Key expansion (FIPS-197 Section 5.2)
361 //
362 // AES-256 (Nk=8, Nr=14):
363 // - The key schedule produces 60 32-bit words (w[0..59]).
364 // - w[0..7] = the original key.
365 // - For i >= 8:
366 // if i mod 8 == 0: w[i] = w[i-8] ^ SubWord(RotWord(w[i-1])) ^ Rcon[i/8-1]
367 // if i mod 8 == 4: w[i] = w[i-8] ^ SubWord(w[i-1])
368 // otherwise: w[i] = w[i-8] ^ w[i-1]
369 // -----------------------------------------------------------------------
370 void key_expansion(const uint8_t key[KEY_SIZE]) {
371 // Nk = 8 words (32 bytes), Nb = 4 words (16 bytes), Nr = 14 rounds
372 // Total words = Nb * (Nr + 1) = 4 * 15 = 60
373 static constexpr int NK = 8; // Key length in 32-bit words
374 static constexpr int TOTAL_WORDS = 4 * (NUM_ROUNDS + 1); // 60
375
376 // Copy the original key into the first 8 words
377 std::memcpy(round_keys_, key, KEY_SIZE);
378
379 // Expand the remaining words
380 uint8_t temp[4];
381 for (int i = NK; i < TOTAL_WORDS; ++i) {
382 // Copy w[i-1] into temp
383 std::memcpy(temp, &round_keys_[(i - 1) * 4], 4);
384
385 if (i % NK == 0) {
386 // RotWord + SubWord + Rcon
389 temp[0] ^= detail::aes::RCON[i / NK - 1];
390 } else if (i % NK == 4) {
391 // AES-256 extra step: SubWord only (no RotWord, no Rcon)
393 }
394
395 // w[i] = w[i-Nk] ^ temp
396 round_keys_[i * 4 + 0] = round_keys_[(i - NK) * 4 + 0] ^ temp[0];
397 round_keys_[i * 4 + 1] = round_keys_[(i - NK) * 4 + 1] ^ temp[1];
398 round_keys_[i * 4 + 2] = round_keys_[(i - NK) * 4 + 2] ^ temp[2];
399 round_keys_[i * 4 + 3] = round_keys_[(i - NK) * 4 + 3] ^ temp[3];
400 }
401 detail::aes::secure_zero(temp, sizeof(temp));
402 }
403
404 // -----------------------------------------------------------------------
405 // AddRoundKey -- XOR state with round key (FIPS-197 Section 5.1.4)
406 // -----------------------------------------------------------------------
407 void add_round_key(uint8_t block[BLOCK_SIZE], int round) const {
408 const uint8_t* rk = &round_keys_[round * BLOCK_SIZE];
409 for (size_t i = 0; i < BLOCK_SIZE; ++i) {
410 block[i] ^= rk[i];
411 }
412 }
413
414 // -----------------------------------------------------------------------
415 // SubBytes -- Apply S-box to every byte (FIPS-197 Section 5.1.1)
416 // -----------------------------------------------------------------------
417 static void sub_bytes(uint8_t block[BLOCK_SIZE]) {
418 for (size_t i = 0; i < BLOCK_SIZE; ++i) {
419 block[i] = detail::aes::SBOX[block[i]];
420 }
421 }
422
423 // -----------------------------------------------------------------------
424 // InvSubBytes -- Apply inverse S-box (FIPS-197 Section 5.3.2)
425 // -----------------------------------------------------------------------
426 static void inv_sub_bytes(uint8_t block[BLOCK_SIZE]) {
427 for (size_t i = 0; i < BLOCK_SIZE; ++i) {
428 block[i] = detail::aes::INV_SBOX[block[i]];
429 }
430 }
431
432 // -----------------------------------------------------------------------
433 // ShiftRows -- Cyclically shift rows left (FIPS-197 Section 5.1.2)
434 //
435 // The state is column-major:
436 // block[0] block[4] block[8] block[12] (row 0: no shift)
437 // block[1] block[5] block[9] block[13] (row 1: shift left 1)
438 // block[2] block[6] block[10] block[14] (row 2: shift left 2)
439 // block[3] block[7] block[11] block[15] (row 3: shift left 3)
440 // -----------------------------------------------------------------------
441 static void shift_rows(uint8_t block[BLOCK_SIZE]) {
442 uint8_t tmp;
443
444 // Row 1: shift left by 1
445 tmp = block[1];
446 block[1] = block[5];
447 block[5] = block[9];
448 block[9] = block[13];
449 block[13] = tmp;
450
451 // Row 2: shift left by 2
452 tmp = block[2];
453 block[2] = block[10];
454 block[10] = tmp;
455 tmp = block[6];
456 block[6] = block[14];
457 block[14] = tmp;
458
459 // Row 3: shift left by 3 (= shift right by 1)
460 tmp = block[15];
461 block[15] = block[11];
462 block[11] = block[7];
463 block[7] = block[3];
464 block[3] = tmp;
465 }
466
467 // -----------------------------------------------------------------------
468 // InvShiftRows -- Cyclically shift rows right (FIPS-197 Section 5.3.1)
469 // -----------------------------------------------------------------------
470 static void inv_shift_rows(uint8_t block[BLOCK_SIZE]) {
471 uint8_t tmp;
472
473 // Row 1: shift right by 1
474 tmp = block[13];
475 block[13] = block[9];
476 block[9] = block[5];
477 block[5] = block[1];
478 block[1] = tmp;
479
480 // Row 2: shift right by 2
481 tmp = block[2];
482 block[2] = block[10];
483 block[10] = tmp;
484 tmp = block[6];
485 block[6] = block[14];
486 block[14] = tmp;
487
488 // Row 3: shift right by 3 (= shift left by 1)
489 tmp = block[3];
490 block[3] = block[7];
491 block[7] = block[11];
492 block[11] = block[15];
493 block[15] = tmp;
494 }
495
496 // -----------------------------------------------------------------------
497 // MixColumns -- Mix each column using GF(2^8) (FIPS-197 Section 5.1.3)
498 //
499 // Each column [s0, s1, s2, s3] is multiplied by the fixed matrix:
500 // [2 3 1 1] [s0]
501 // [1 2 3 1] * [s1]
502 // [1 1 2 3] [s2]
503 // [3 1 1 2] [s3]
504 // -----------------------------------------------------------------------
505 static void mix_columns(uint8_t block[BLOCK_SIZE]) {
506 for (int col = 0; col < 4; ++col) {
507 int i = col * 4; // Column starts at block[i]
508
509 uint8_t s0 = block[i + 0];
510 uint8_t s1 = block[i + 1];
511 uint8_t s2 = block[i + 2];
512 uint8_t s3 = block[i + 3];
513
514 block[i + 0] = detail::aes::gf_mul(2, s0) ^ detail::aes::gf_mul(3, s1)
515 ^ s2 ^ s3;
516 block[i + 1] = s0 ^ detail::aes::gf_mul(2, s1) ^ detail::aes::gf_mul(3, s2)
517 ^ s3;
518 block[i + 2] = s0 ^ s1 ^ detail::aes::gf_mul(2, s2)
519 ^ detail::aes::gf_mul(3, s3);
520 block[i + 3] = detail::aes::gf_mul(3, s0) ^ s1 ^ s2
521 ^ detail::aes::gf_mul(2, s3);
522 }
523 }
524
525 // -----------------------------------------------------------------------
526 // InvMixColumns -- Inverse of MixColumns (FIPS-197 Section 5.3.3)
527 //
528 // Each column is multiplied by the inverse matrix:
529 // [14 11 13 9] [s0]
530 // [ 9 14 11 13] * [s1]
531 // [13 9 14 11] [s2]
532 // [11 13 9 14] [s3]
533 // -----------------------------------------------------------------------
534 static void inv_mix_columns(uint8_t block[BLOCK_SIZE]) {
535 for (int col = 0; col < 4; ++col) {
536 int i = col * 4;
537
538 uint8_t s0 = block[i + 0];
539 uint8_t s1 = block[i + 1];
540 uint8_t s2 = block[i + 2];
541 uint8_t s3 = block[i + 3];
542
543 block[i + 0] = detail::aes::gf_mul(14, s0) ^ detail::aes::gf_mul(11, s1)
544 ^ detail::aes::gf_mul(13, s2) ^ detail::aes::gf_mul( 9, s3);
545 block[i + 1] = detail::aes::gf_mul( 9, s0) ^ detail::aes::gf_mul(14, s1)
546 ^ detail::aes::gf_mul(11, s2) ^ detail::aes::gf_mul(13, s3);
547 block[i + 2] = detail::aes::gf_mul(13, s0) ^ detail::aes::gf_mul( 9, s1)
548 ^ detail::aes::gf_mul(14, s2) ^ detail::aes::gf_mul(11, s3);
549 block[i + 3] = detail::aes::gf_mul(11, s0) ^ detail::aes::gf_mul(13, s1)
550 ^ detail::aes::gf_mul( 9, s2) ^ detail::aes::gf_mul(14, s3);
551 }
552 }
553};
554
555} // namespace signet::forge::crypto
AES-256 block cipher (FIPS-197).
Definition aes_core.hpp:253
Aes256 & operator=(Aes256 &&other) noexcept
Definition aes_core.hpp:267
Aes256(const uint8_t key[KEY_SIZE])
Initialize with a 32-byte key. Performs key expansion immediately.
Definition aes_core.hpp:260
Aes256 & operator=(const Aes256 &)=delete
Aes256(const Aes256 &)=delete
~Aes256()
Destructor: securely zero round keys to prevent key material leakage.
Definition aes_core.hpp:270
void encrypt_block(uint8_t block[BLOCK_SIZE]) const
Encrypt a single 16-byte block in-place (FIPS-197 Section 5.1).
Definition aes_core.hpp:280
Aes256(Aes256 &&other) noexcept
Definition aes_core.hpp:266
static constexpr size_t KEY_SIZE
Key size in bytes (256 bits).
Definition aes_core.hpp:255
static constexpr size_t BLOCK_SIZE
Block size in bytes (128 bits).
Definition aes_core.hpp:256
static constexpr int NUM_ROUNDS
Number of AES-256 rounds.
Definition aes_core.hpp:257
void decrypt_block(uint8_t block[BLOCK_SIZE]) const
Decrypt a single 16-byte block in-place (FIPS-197 Section 5.3).
Definition aes_core.hpp:328
void secure_zero(void *ptr, size_t len)
Securely zero memory that held key material (CWE-244, NIST SP 800-38D §8.3).
Definition aes_core.hpp:203
void rot_word(uint8_t word[4])
Rotate a 4-byte word left by one byte: [a,b,c,d] -> [b,c,d,a].
Definition aes_core.hpp:189
bool has_hardware_aes() noexcept
Check if CPU supports AES-NI (x86) or ARMv8-CE AES (ARM).
Definition aes_core.hpp:217
constexpr uint8_t gf_mul(uint8_t a, uint8_t b)
Multiply two elements in GF(2^8) using the Russian peasant algorithm.
Definition aes_core.hpp:165
constexpr uint8_t xtime(uint8_t a)
Multiply by x (i.e., by 2) in GF(2^8).
Definition aes_core.hpp:158
void sub_word(uint8_t word[4])
Apply S-box to each byte of a 4-byte word.
Definition aes_core.hpp:181
constexpr uint64_t round(uint64_t acc, uint64_t lane)
Round function: accumulate one 8-byte lane into an accumulator.
Definition xxhash.hpp:107