Signet Forge 0.1.0
C++20 Parquet library with AI-native extensions
DEMO
Loading...
Searching...
No Matches
aes_gcm.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_gcm.hpp -- Bundled, zero-dependency, header-only AES-256-GCM
10//
11// Implements AES-256 in Galois/Counter Mode (GCM) as specified in:
12// NIST SP 800-38D: Recommendation for Block Cipher Modes of Operation:
13// Galois/Counter Mode (GCM) and GMAC
14// https://csrc.nist.gov/publications/detail/sp/800-38d/final
15//
16// GCM provides both confidentiality (encryption) and authenticity (128-bit
17// authentication tag). This is the mode used for Parquet footer encryption
18// where tamper detection is critical.
19//
20// Parameters:
21// Key size: 32 bytes (256 bits, AES-256)
22// IV size: 12 bytes (96 bits, standard GCM nonce)
23// Tag size: 16 bytes (128 bits, full-length authentication tag)
24//
25// NIST SP 800-38D test vector (Test Case 16 -- AES-256-GCM):
26// Key: feffe9928665731c6d6a8f9467308308
27// feffe9928665731c6d6a8f9467308308
28// IV: cafebabefacedbaddecaf888
29// AAD: feedfacedeadbeeffeedfacedeadbeef
30// abaddad2
31// Plaintext: d9313225f88406e5a55909c5aff5269a
32// 86a7a9531534f7da2e4c303d8a318a72
33// 1c3c0c95956809532fcf0e2449a6b525
34// b16aedf5aa0de657ba637b39
35// Ciphertext: 522dc1f099567d07f47f37a32a84427d
36// 643a8cdcbfe5c0c97598a2bd2555d1aa
37// 8cb08e48590dbb3da7b08b1056828838
38// c5f61e6393ba7a0abcc9f662
39// Tag: 76fc6ece0f4e1768cddf8853bb2d551b
40// ---------------------------------------------------------------------------
41
43#include "signet/error.hpp"
44
45#include <cstddef>
46#include <cstdint>
47#include <cstring>
48#include <vector>
49
50namespace signet::forge::crypto {
51
52// ===========================================================================
53// GCM internal helpers
54// ===========================================================================
55namespace detail::gcm {
56
57// ---------------------------------------------------------------------------
58// GF(2^128) multiplication for GHASH
59//
60// The GCM spec uses the polynomial x^128 + x^7 + x^2 + x + 1 over GF(2).
61// The 128-bit reduction constant (when the high bit is shifted out) is:
62// R = 0xe1000000 00000000 00000000 00000000
63//
64// We represent 128-bit values as two uint64_t in big-endian order:
65// v[0] = most significant 64 bits
66// v[1] = least significant 64 bits
67//
68// This uses the "schoolbook" bit-by-bit multiplication, which is correct
69// and simple though not the fastest possible implementation.
70// ---------------------------------------------------------------------------
71
76struct Block128 {
77 uint64_t hi;
78 uint64_t lo;
79
81 Block128() : hi(0), lo(0) {}
82
84 Block128(uint64_t h, uint64_t l) : hi(h), lo(l) {}
85};
86
88inline Block128 load_block(const uint8_t src[16]) {
89 Block128 b;
90 b.hi = 0;
91 b.lo = 0;
92 for (int i = 0; i < 8; ++i) {
93 b.hi = (b.hi << 8) | src[i];
94 }
95 for (int i = 8; i < 16; ++i) {
96 b.lo = (b.lo << 8) | src[i];
97 }
98 return b;
99}
100
102inline void store_block(uint8_t dst[16], const Block128& b) {
103 for (int i = 7; i >= 0; --i) {
104 dst[i] = static_cast<uint8_t>(b.hi >> (8 * (7 - i)));
105 dst[i + 8] = static_cast<uint8_t>(b.lo >> (8 * (7 - i)));
106 }
107}
108
110inline Block128 xor_blocks(const Block128& a, const Block128& b) {
111 return {a.hi ^ b.hi, a.lo ^ b.lo};
112}
113
119 uint64_t lsb = V.lo & 1;
120 Block128 result;
121 result.lo = (V.lo >> 1) | ((V.hi & 1) << 63);
122 result.hi = (V.hi >> 1) ^ ((uint64_t(0xe1) << 56) & (uint64_t(0) - lsb));
123 return result;
124}
125
135
137 void init(const Block128& H) {
138 entries[0] = Block128{0, 0};
139 entries[1] = H;
140 entries[2] = gf128_double(H);
143 // Fill remaining entries by XOR (linearity over GF(2))
144 entries[3] = xor_blocks(entries[2], entries[1]);
145 entries[5] = xor_blocks(entries[4], entries[1]);
146 entries[6] = xor_blocks(entries[4], entries[2]);
147 entries[7] = xor_blocks(entries[4], entries[3]);
148 entries[9] = xor_blocks(entries[8], entries[1]);
149 entries[10] = xor_blocks(entries[8], entries[2]);
150 entries[11] = xor_blocks(entries[8], entries[3]);
151 entries[12] = xor_blocks(entries[8], entries[4]);
152 entries[13] = xor_blocks(entries[8], entries[5]);
153 entries[14] = xor_blocks(entries[8], entries[6]);
154 entries[15] = xor_blocks(entries[8], entries[7]);
155 }
156};
157
162inline Block128 ct_table_lookup(const Block128 table[16], uint8_t index) {
163 Block128 result{0, 0};
164 for (uint8_t i = 0; i < 16; ++i) {
165 // Branchless equality: arithmetic instead of == to avoid conditional branch (CWE-208)
166 uint64_t eq = static_cast<uint64_t>(i) ^ static_cast<uint64_t>(index);
167 uint64_t mask = ((eq | (~eq + 1)) >> 63) ^ 1;
168 mask = static_cast<uint64_t>(0) - mask;
169 result.hi |= table[i].hi & mask;
170 result.lo |= table[i].lo & mask;
171 }
172 return result;
173}
174
181inline uint64_t ct_reduce4(uint8_t index) {
182 static constexpr uint64_t R4[16] = {
183 0x0000000000000000ULL, 0xe100000000000000ULL,
184 0x7080000000000000ULL, 0x9180000000000000ULL,
185 0x3840000000000000ULL, 0xd940000000000000ULL,
186 0x48c0000000000000ULL, 0xa9c0000000000000ULL,
187 0x1c20000000000000ULL, 0xfd20000000000000ULL,
188 0x6ca0000000000000ULL, 0x8da0000000000000ULL,
189 0x2460000000000000ULL, 0xc560000000000000ULL,
190 0x54e0000000000000ULL, 0xb5e0000000000000ULL,
191 };
192 uint64_t result = 0;
193 for (uint8_t i = 0; i < 16; ++i) {
194 // Branchless equality: arithmetic instead of == to avoid conditional branch (CWE-208)
195 uint64_t eq = static_cast<uint64_t>(i) ^ static_cast<uint64_t>(index);
196 uint64_t mask = ((eq | (~eq + 1)) >> 63) ^ 1;
197 mask = static_cast<uint64_t>(0) - mask;
198 result |= R4[i] & mask;
199 }
200 return result;
201}
202
211inline uint8_t rev4(uint8_t n) {
212 return static_cast<uint8_t>(
213 ((n & 1) << 3) | ((n & 2) << 1) | ((n & 4) >> 1) | ((n & 8) >> 3));
214}
215
229inline Block128 gf128_mul_ct(const GHashTable& table, const Block128& X) {
230 Block128 Z{0, 0};
231 uint8_t x_bytes[16];
232 store_block(x_bytes, X);
233
234 // Process from last byte to first (reversed Horner for correct GCM ordering)
235 for (int byte_idx = 15; byte_idx >= 0; --byte_idx) {
236 uint8_t b = x_bytes[byte_idx];
237
238 // Process low nibble first (higher polynomial degrees, innermost Horner)
239 {
240 uint8_t rem = static_cast<uint8_t>(Z.lo & 0x0F);
241 Z.lo = (Z.lo >> 4) | (Z.hi << 60);
242 Z.hi = (Z.hi >> 4) ^ ct_reduce4(rev4(rem));
243 Block128 entry = ct_table_lookup(table.entries,
244 rev4(static_cast<uint8_t>(b & 0x0F)));
245 Z = xor_blocks(Z, entry);
246 }
247
248 // Process high nibble (lower polynomial degrees)
249 {
250 uint8_t rem = static_cast<uint8_t>(Z.lo & 0x0F);
251 Z.lo = (Z.lo >> 4) | (Z.hi << 60);
252 Z.hi = (Z.hi >> 4) ^ ct_reduce4(rev4(rem));
253 Block128 entry = ct_table_lookup(table.entries,
254 rev4(static_cast<uint8_t>((b >> 4) & 0x0F)));
255 Z = xor_blocks(Z, entry);
256 }
257 }
258
259 return Z;
260}
261
268inline Block128 gf128_mul(const Block128& X, const Block128& Y) {
269 Block128 Z; // Accumulator, starts at 0
270 Block128 V = Y; // Working copy
271
272 // Iterate over all 128 bits of X
273 for (int i = 0; i < 128; ++i) {
274 // Check if bit i of X is set (GCM bit ordering: MSB first)
275 uint64_t word = (i < 64) ? X.hi : X.lo;
276 int bit_pos = (i < 64) ? (63 - i) : (63 - (i - 64));
277 if (word & (uint64_t(1) << bit_pos)) {
278 Z = xor_blocks(Z, V);
279 }
280
281 // Check LSB of V (bit 127 in GCM ordering = bit 0 of lo)
282 bool lsb = (V.lo & 1) != 0;
283
284 // Right-shift V by 1 bit
285 V.lo = (V.lo >> 1) | ((V.hi & 1) << 63);
286 V.hi = V.hi >> 1;
287
288 // If LSB was set, XOR with R = 0xe100...0
289 if (lsb) {
290 V.hi ^= uint64_t(0xe1) << 56;
291 }
292 }
293
294 return Z;
295}
296
304[[deprecated("Use ghash_update (constant-time) instead")]]
305inline Block128 ghash(const Block128& H,
306 const uint8_t* data, size_t data_size) {
307 Block128 X; // X_0 = 0
308
309 size_t full_blocks = data_size / 16;
310 for (size_t i = 0; i < full_blocks; ++i) {
311 Block128 block = load_block(data + i * 16);
312 X = xor_blocks(X, block);
313 X = gf128_mul(X, H);
314 }
315
316 // Handle partial last block (zero-padded)
317 size_t remainder = data_size % 16;
318 if (remainder > 0) {
319 uint8_t padded[16] = {};
320 std::memcpy(padded, data + full_blocks * 16, remainder);
321 Block128 block = load_block(padded);
322 X = xor_blocks(X, block);
323 X = gf128_mul(X, H);
324 }
325
326 return X;
327}
328
330inline void inc32(uint8_t counter[16]) {
331 // The counter occupies bytes 12..15 (big-endian uint32)
332 for (int i = 15; i >= 12; --i) {
333 if (++counter[i] != 0) break;
334 }
335}
336
350inline void gctr(const Aes256& cipher,
351 const uint8_t icb[16],
352 const uint8_t* input, size_t input_size,
353 uint8_t* output) {
354 if (input_size == 0) return;
355
356 const size_t num_blocks = (input_size + 15) / 16;
357 // NIST SP 800-38D §5.2.1.1: 32-bit counter must not wrap (max 2^32-2 blocks).
358 // Protected by MAX_GCM_PLAINTEXT guard in calling code — this is a defense-in-depth check.
359 if (num_blocks > 0xFFFFFFFEULL) return;
360
361 uint8_t counter[16];
362 std::memcpy(counter, icb, 16);
363
364 size_t full_blocks = input_size / 16;
365 for (size_t i = 0; i < full_blocks; ++i) {
366 uint8_t encrypted_counter[16];
367 std::memcpy(encrypted_counter, counter, 16);
368 cipher.encrypt_block(encrypted_counter);
369
370 for (int j = 0; j < 16; ++j) {
371 output[i * 16 + j] = input[i * 16 + j] ^ encrypted_counter[j];
372 }
373
374 inc32(counter);
375 }
376
377 // Handle partial last block
378 size_t remainder = input_size % 16;
379 if (remainder > 0) {
380 uint8_t encrypted_counter[16];
381 std::memcpy(encrypted_counter, counter, 16);
382 cipher.encrypt_block(encrypted_counter);
383
384 size_t offset = full_blocks * 16;
385 for (size_t j = 0; j < remainder; ++j) {
386 output[offset + j] = input[offset + j] ^ encrypted_counter[j];
387 }
388 }
389}
390
391} // namespace detail::gcm
392
393// ===========================================================================
394// AesGcm -- AES-256-GCM authenticated encryption (NIST SP 800-38D)
395// ===========================================================================
396
406class AesGcm {
407public:
408 static constexpr size_t KEY_SIZE = 32;
409 static constexpr size_t IV_SIZE = 12;
410 static constexpr size_t TAG_SIZE = 16;
411
415 explicit AesGcm(const uint8_t key[KEY_SIZE])
416 : cipher_(key) {
417 // Compute hash subkey: H = AES_K(0^128)
418 uint8_t zero_block[16] = {};
419 cipher_.encrypt_block(zero_block);
420 auto H = detail::gcm::load_block(zero_block);
421 H_table_.init(H);
422 }
423
424 // -----------------------------------------------------------------------
425 // encrypt -- Authenticated encryption with additional data (AEAD)
426 //
427 // Inputs:
428 // plaintext / plaintext_size: data to encrypt
429 // iv: 12-byte nonce (MUST be unique per message under the same key)
430 // aad / aad_size: additional authenticated data (authenticated but not
431 // encrypted; may be nullptr if aad_size == 0)
432 //
433 // Output:
434 // ciphertext (same size as plaintext) with 16-byte auth tag appended.
435 // Total output size = plaintext_size + TAG_SIZE.
436 //
437 // Algorithm (NIST SP 800-38D Section 7.1):
438 // 1. J0 = IV || 0x00000001 (initial counter block)
439 // 2. ICB = inc32(J0) (first counter for encryption)
440 // 3. C = GCTR_K(ICB, P) (encrypt plaintext)
441 // 4. S = GHASH_H(A || pad || C || pad || [len(A)]_64 || [len(C)]_64)
442 // 5. T = MSB_t(GCTR_K(J0, S)) (authentication tag)
443 // -----------------------------------------------------------------------
449 void set_iv_size(size_t size) {
450 if (size != 12 && size != 16) {
451 throw std::invalid_argument("AES-GCM: IV size must be 12 or 16 bytes");
452 }
453 iv_size_ = size;
454 }
455
457 [[nodiscard]] size_t iv_size() const { return iv_size_; }
458
462 static constexpr uint64_t MAX_GCM_PLAINTEXT =
463 (static_cast<uint64_t>(UINT32_MAX) - 1) * 16; // ~64 GB
464
467 static constexpr uint64_t MAX_AAD_BYTES = (UINT64_MAX / 8);
468
469 // Gap C-6: Compile-time assertion that TAG_SIZE is the full 128 bits.
470 // NIST SP 800-38D §5.2.1.2 specifies allowed tag lengths {128,120,112,104,96,64,32}.
471 // Truncated tags weaken authentication strength. Signet enforces full 128-bit tags
472 // only — no truncation API is exposed. This static_assert guards against accidental
473 // changes to TAG_SIZE.
474 static_assert(TAG_SIZE == 16,
475 "GCM tag truncation prohibited (NIST SP 800-38D §5.2.1.2, CWE-328)");
476
488 const uint8_t* plaintext, size_t plaintext_size,
489 const uint8_t iv[IV_SIZE],
490 const uint8_t* aad = nullptr, size_t aad_size = 0) const {
491
492 using namespace detail::gcm;
493
494 if (static_cast<uint64_t>(plaintext_size) > MAX_GCM_PLAINTEXT) {
496 "AES-GCM: plaintext exceeds NIST SP 800-38D maximum"};
497 }
498 // Gap C-12: AAD length limit per NIST SP 800-38D §5.2.1.1
499 if (static_cast<uint64_t>(aad_size) > MAX_AAD_BYTES) {
501 "AES-GCM: AAD exceeds NIST SP 800-38D §5.2.1.1 maximum (2^64-1 bits)"};
502 }
503
504 // Step 1: Derive J0 from IV (supports both 12-byte and 16-byte IVs)
505 uint8_t J0[16] = {};
506 derive_j0(iv, J0);
507
508 // Step 2: Form ICB = inc32(J0) -- first counter for GCTR
509 uint8_t ICB[16];
510 std::memcpy(ICB, J0, 16);
511 inc32(ICB); // Now counter = 2
512
513 // Step 3: Encrypt plaintext with GCTR
514 std::vector<uint8_t> output(plaintext_size + TAG_SIZE);
515 gctr(cipher_, ICB, plaintext, plaintext_size, output.data());
516
517 // Step 4: Compute GHASH over AAD and ciphertext
518 // S = GHASH_H(A || 0* || C || 0* || [len(A)]_64 || [len(C)]_64)
519 //
520 // We compute this incrementally:
521 // X = GHASH(H, AAD_padded)
522 // X = continue GHASH with ciphertext_padded
523 // X = continue GHASH with length block
524 Block128 X; // X_0 = 0
525
526 // Process AAD blocks
527 if (aad != nullptr && aad_size > 0) {
528 X = ghash_update(X, aad, aad_size);
529 }
530
531 // Process ciphertext blocks
532 if (plaintext_size > 0) {
533 X = ghash_update(X, output.data(), plaintext_size);
534 }
535
536 // Process length block: [len(A) in bits]_64 || [len(C) in bits]_64
537 uint8_t len_block[16] = {};
538 uint64_t aad_bits = static_cast<uint64_t>(aad_size) * 8;
539 uint64_t ct_bits = static_cast<uint64_t>(plaintext_size) * 8;
540 // Store as big-endian uint64
541 for (int i = 0; i < 8; ++i) {
542 len_block[7 - i] = static_cast<uint8_t>(aad_bits >> (8 * i));
543 len_block[15 - i] = static_cast<uint8_t>(ct_bits >> (8 * i));
544 }
545 Block128 len_b = load_block(len_block);
546 X = xor_blocks(X, len_b);
547 X = gf128_mul_ct(H_table_, X);
548
549 // Step 5: Compute authentication tag T = GCTR_K(J0, S)
550 uint8_t S[16];
551 store_block(S, X);
552 uint8_t tag[16];
553 gctr(cipher_, J0, S, 16, tag);
554
555 // Append tag to output
556 std::memcpy(output.data() + plaintext_size, tag, TAG_SIZE);
557
558 return output;
559 }
560
561 // -----------------------------------------------------------------------
562 // decrypt -- Authenticated decryption and verification
563 //
564 // Inputs:
565 // ciphertext_with_tag / total_size: ciphertext + 16-byte appended tag.
566 // total_size must be >= TAG_SIZE.
567 // iv: 12-byte nonce (same as used for encryption)
568 // aad / aad_size: additional authenticated data
569 //
570 // Output:
571 // On success: plaintext (total_size - TAG_SIZE bytes)
572 // On failure: ENCRYPTION_ERROR if authentication tag does not match
573 //
574 // Algorithm (NIST SP 800-38D Section 7.2):
575 // 1. Separate C and T from the input
576 // 2. J0 = IV || 0x00000001
577 // 3. ICB = inc32(J0)
578 // 4. P = GCTR_K(ICB, C) (decrypt ciphertext)
579 // 5. S = GHASH_H(A || pad || C || pad || len_block)
580 // 6. T' = MSB_t(GCTR_K(J0, S)) (recompute tag)
581 // 7. If T != T': return error (authentication failed)
582 // -----------------------------------------------------------------------
593 const uint8_t* ciphertext_with_tag, size_t total_size,
594 const uint8_t iv[IV_SIZE],
595 const uint8_t* aad = nullptr, size_t aad_size = 0) const {
596
597 using namespace detail::gcm;
598
599 // Validate minimum size
600 if (total_size < TAG_SIZE) {
602 "AES-GCM: input too short for authentication tag"};
603 }
604
605 if (static_cast<uint64_t>(total_size - TAG_SIZE) > MAX_GCM_PLAINTEXT) {
607 "AES-GCM: ciphertext exceeds NIST SP 800-38D maximum"};
608 }
609 // Gap C-12: AAD length limit per NIST SP 800-38D §5.2.1.1
610 if (static_cast<uint64_t>(aad_size) > MAX_AAD_BYTES) {
612 "AES-GCM: AAD exceeds NIST SP 800-38D §5.2.1.1 maximum"};
613 }
614
615 size_t ciphertext_size = total_size - TAG_SIZE;
616 const uint8_t* ciphertext = ciphertext_with_tag;
617 const uint8_t* received_tag = ciphertext_with_tag + ciphertext_size;
618
619 // Step 1: Derive J0 from IV (supports both 12-byte and 16-byte IVs)
620 uint8_t J0[16] = {};
621 derive_j0(iv, J0);
622
623 // Step 2: Recompute GHASH over AAD and ciphertext (BEFORE decryption)
624 Block128 X;
625
626 // Process AAD blocks
627 if (aad != nullptr && aad_size > 0) {
628 X = ghash_update(X, aad, aad_size);
629 }
630
631 // Process ciphertext blocks
632 if (ciphertext_size > 0) {
633 X = ghash_update(X, ciphertext, ciphertext_size);
634 }
635
636 // Process length block
637 uint8_t len_block[16] = {};
638 uint64_t aad_bits = static_cast<uint64_t>(aad_size) * 8;
639 uint64_t ct_bits = static_cast<uint64_t>(ciphertext_size) * 8;
640 for (int i = 0; i < 8; ++i) {
641 len_block[7 - i] = static_cast<uint8_t>(aad_bits >> (8 * i));
642 len_block[15 - i] = static_cast<uint8_t>(ct_bits >> (8 * i));
643 }
644 Block128 len_b = load_block(len_block);
645 X = xor_blocks(X, len_b);
646 X = gf128_mul_ct(H_table_, X);
647
648 // Step 3: Compute expected tag T' = GCTR_K(J0, S)
649 uint8_t S[16];
650 store_block(S, X);
651 uint8_t expected_tag[16];
652 gctr(cipher_, J0, S, 16, expected_tag);
653
654 // Step 4: Constant-time tag comparison (prevents timing attacks)
655 uint8_t diff = 0;
656 for (int i = 0; i < static_cast<int>(TAG_SIZE); ++i) {
657 diff |= received_tag[i] ^ expected_tag[i];
658 }
659 if (diff != 0) {
661 "AES-GCM: authentication tag mismatch"};
662 }
663
664 // Step 5: Decrypt ciphertext
665 uint8_t ICB[16];
666 std::memcpy(ICB, J0, 16);
667 inc32(ICB);
668
669 std::vector<uint8_t> plaintext(ciphertext_size);
670 gctr(cipher_, ICB, ciphertext, ciphertext_size, plaintext.data());
671
672 return plaintext;
673 }
674
675private:
676 Aes256 cipher_;
677 detail::gcm::GHashTable H_table_;
678 size_t iv_size_{IV_SIZE};
679
683 void derive_j0(const uint8_t* iv, uint8_t j0[16]) const {
684 using namespace detail::gcm;
685 if (iv_size_ == 12) {
686 std::memcpy(j0, iv, 12);
687 j0[12] = 0x00; j0[13] = 0x00; j0[14] = 0x00; j0[15] = 0x01;
688 } else {
689 // GHASH-based derivation for non-96-bit IV (§5.2.1.2)
690 Block128 X{};
691 // Process IV as GHASH input (single 16-byte block)
692 Block128 iv_block = load_block(iv);
693 X = xor_blocks(X, iv_block);
694 X = gf128_mul_ct(H_table_, X);
695 // Length block: [0]_64 || [len(IV) in bits]_64
696 uint8_t len_block[16] = {};
697 uint64_t iv_bits = static_cast<uint64_t>(iv_size_) * 8;
698 for (int i = 0; i < 8; ++i) {
699 len_block[15 - i] = static_cast<uint8_t>(iv_bits >> (8 * i));
700 }
701 Block128 lb = load_block(len_block);
702 X = xor_blocks(X, lb);
703 X = gf128_mul_ct(H_table_, X);
704 store_block(j0, X);
705 }
706 }
707
710 detail::gcm::Block128 ghash_update(
711 detail::gcm::Block128 X,
712 const uint8_t* data, size_t data_size) const {
713
714 using namespace detail::gcm;
715
716 size_t full_blocks = data_size / 16;
717 for (size_t i = 0; i < full_blocks; ++i) {
718 Block128 block = load_block(data + i * 16);
719 X = xor_blocks(X, block);
720 X = gf128_mul_ct(H_table_, X);
721 }
722
723 // Handle partial last block (zero-padded)
724 size_t remainder = data_size % 16;
725 if (remainder > 0) {
726 uint8_t padded[16] = {};
727 std::memcpy(padded, data + full_blocks * 16, remainder);
728 Block128 block = load_block(padded);
729 X = xor_blocks(X, block);
730 X = gf128_mul_ct(H_table_, X);
731 }
732
733 return X;
734 }
735};
736
737} // namespace signet::forge::crypto
AES-256 block cipher implementation (FIPS-197).
AES-256 block cipher (FIPS-197).
Definition aes_core.hpp:253
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
AES-256 in Galois/Counter Mode (GCM) as specified in NIST SP 800-38D.
Definition aes_gcm.hpp:406
expected< std::vector< uint8_t > > encrypt(const uint8_t *plaintext, size_t plaintext_size, const uint8_t iv[IV_SIZE], const uint8_t *aad=nullptr, size_t aad_size=0) const
Authenticated encryption with additional data (AEAD).
Definition aes_gcm.hpp:487
expected< std::vector< uint8_t > > decrypt(const uint8_t *ciphertext_with_tag, size_t total_size, const uint8_t iv[IV_SIZE], const uint8_t *aad=nullptr, size_t aad_size=0) const
Authenticated decryption and verification (NIST SP 800-38D Section 7.2).
Definition aes_gcm.hpp:592
static constexpr uint64_t MAX_AAD_BYTES
NIST SP 800-38D §5.2.1.1: AAD length limit is 2^64-1 bits.
Definition aes_gcm.hpp:467
static constexpr size_t KEY_SIZE
AES-256 key size in bytes.
Definition aes_gcm.hpp:408
AesGcm(const uint8_t key[KEY_SIZE])
Initialize with a 32-byte key.
Definition aes_gcm.hpp:415
static constexpr size_t TAG_SIZE
Authentication tag size in bytes (128 bits).
Definition aes_gcm.hpp:410
static constexpr uint64_t MAX_GCM_PLAINTEXT
Maximum plaintext size for a single GCM invocation (NIST SP 800-38D §5.2.1.1).
Definition aes_gcm.hpp:462
static constexpr size_t IV_SIZE
Standard GCM nonce size in bytes (96 bits).
Definition aes_gcm.hpp:409
void set_iv_size(size_t size)
Set the expected IV size.
Definition aes_gcm.hpp:449
size_t iv_size() const
Get the current IV size (12 or 16 bytes).
Definition aes_gcm.hpp:457
A lightweight result type that holds either a success value of type T or an Error.
Definition error.hpp:145
uint8_t rev4(uint8_t n)
Bit-reverse a 4-bit nibble value.
Definition aes_gcm.hpp:211
Block128 load_block(const uint8_t src[16])
Load a 16-byte array into a Block128 (big-endian byte order).
Definition aes_gcm.hpp:88
void store_block(uint8_t dst[16], const Block128 &b)
Store a Block128 into a 16-byte array (big-endian byte order).
Definition aes_gcm.hpp:102
Block128 ghash(const Block128 &H, const uint8_t *data, size_t data_size)
GHASH: Compute the GHASH function over data using hash subkey H.
Definition aes_gcm.hpp:305
Block128 gf128_double(const Block128 &V)
"Doubling" in GF(2^128): multiply by x (shift right by 1 in GCM bit ordering).
Definition aes_gcm.hpp:118
uint64_t ct_reduce4(uint8_t index)
Constant-time 4-bit reduction table lookup.
Definition aes_gcm.hpp:181
Block128 xor_blocks(const Block128 &a, const Block128 &b)
XOR two Block128 values.
Definition aes_gcm.hpp:110
void inc32(uint8_t counter[16])
Increment the rightmost 32 bits of a 16-byte counter block (big-endian).
Definition aes_gcm.hpp:330
Block128 gf128_mul(const Block128 &X, const Block128 &Y)
Multiply two elements in GF(2^128) using the schoolbook algorithm.
Definition aes_gcm.hpp:268
void gctr(const Aes256 &cipher, const uint8_t icb[16], const uint8_t *input, size_t input_size, uint8_t *output)
GCTR: AES-CTR encryption with the given initial counter block.
Definition aes_gcm.hpp:350
Block128 ct_table_lookup(const Block128 table[16], uint8_t index)
Constant-time 16-entry Block128 table lookup.
Definition aes_gcm.hpp:162
Block128 gf128_mul_ct(const GHashTable &table, const Block128 &X)
Constant-time GF(2^128) multiplication using the 4-bit precomputed table.
Definition aes_gcm.hpp:229
@ ENCRYPTION_ERROR
An encryption or decryption operation failed (bad key, tampered ciphertext, PME error).
Lightweight error value carrying an ErrorCode and a human-readable message.
Definition error.hpp:101
128-bit value stored as two big-endian uint64_t halves.
Definition aes_gcm.hpp:76
Block128()
Default constructor – zero-initializes both halves.
Definition aes_gcm.hpp:81
uint64_t hi
Bits 127..64 (most significant half).
Definition aes_gcm.hpp:77
uint64_t lo
Bits 63..0 (least significant half).
Definition aes_gcm.hpp:78
Block128(uint64_t h, uint64_t l)
Construct from explicit high and low halves.
Definition aes_gcm.hpp:84
4-bit precomputed table for constant-time GHASH multiplication.
Definition aes_gcm.hpp:133
void init(const Block128 &H)
Precompute the 16-entry multiplication table from hash subkey H.
Definition aes_gcm.hpp:137