55namespace detail::gcm {
92 for (
int i = 0; i < 8; ++i) {
93 b.
hi = (b.
hi << 8) | src[i];
95 for (
int i = 8; i < 16; ++i) {
96 b.
lo = (b.
lo << 8) | src[i];
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)));
119 uint64_t lsb = V.
lo & 1;
121 result.
lo = (V.
lo >> 1) | ((V.
hi & 1) << 63);
122 result.
hi = (V.
hi >> 1) ^ ((uint64_t(0xe1) << 56) & (uint64_t(0) - lsb));
164 for (uint8_t i = 0; i < 16; ++i) {
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;
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,
193 for (uint8_t i = 0; i < 16; ++i) {
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;
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));
235 for (
int byte_idx = 15; byte_idx >= 0; --byte_idx) {
236 uint8_t b = x_bytes[byte_idx];
240 uint8_t rem =
static_cast<uint8_t
>(Z.lo & 0x0F);
241 Z.lo = (Z.lo >> 4) | (Z.hi << 60);
244 rev4(
static_cast<uint8_t
>(b & 0x0F)));
250 uint8_t rem =
static_cast<uint8_t
>(Z.lo & 0x0F);
251 Z.lo = (Z.lo >> 4) | (Z.hi << 60);
254 rev4(
static_cast<uint8_t
>((b >> 4) & 0x0F)));
273 for (
int i = 0; i < 128; ++i) {
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)) {
282 bool lsb = (V.
lo & 1) != 0;
285 V.
lo = (V.
lo >> 1) | ((V.
hi & 1) << 63);
290 V.
hi ^= uint64_t(0xe1) << 56;
304[[deprecated(
"Use ghash_update (constant-time) instead")]]
306 const uint8_t* data,
size_t data_size) {
309 size_t full_blocks = data_size / 16;
310 for (
size_t i = 0; i < full_blocks; ++i) {
317 size_t remainder = data_size % 16;
319 uint8_t padded[16] = {};
320 std::memcpy(padded, data + full_blocks * 16, remainder);
330inline void inc32(uint8_t counter[16]) {
332 for (
int i = 15; i >= 12; --i) {
333 if (++counter[i] != 0)
break;
351 const uint8_t icb[16],
352 const uint8_t* input,
size_t input_size,
354 if (input_size == 0)
return;
356 const size_t num_blocks = (input_size + 15) / 16;
359 if (num_blocks > 0xFFFFFFFEULL)
return;
362 std::memcpy(counter, icb, 16);
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);
370 for (
int j = 0; j < 16; ++j) {
371 output[i * 16 + j] = input[i * 16 + j] ^ encrypted_counter[j];
378 size_t remainder = input_size % 16;
380 uint8_t encrypted_counter[16];
381 std::memcpy(encrypted_counter, counter, 16);
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];
418 uint8_t zero_block[16] = {};
450 if (size != 12 && size != 16) {
451 throw std::invalid_argument(
"AES-GCM: IV size must be 12 or 16 bytes");
457 [[nodiscard]]
size_t iv_size()
const {
return iv_size_; }
463 (
static_cast<uint64_t
>(UINT32_MAX) - 1) * 16;
475 "GCM tag truncation prohibited (NIST SP 800-38D §5.2.1.2, CWE-328)");
488 const uint8_t* plaintext,
size_t plaintext_size,
490 const uint8_t* aad =
nullptr,
size_t aad_size = 0)
const {
492 using namespace detail::gcm;
496 "AES-GCM: plaintext exceeds NIST SP 800-38D maximum"};
501 "AES-GCM: AAD exceeds NIST SP 800-38D §5.2.1.1 maximum (2^64-1 bits)"};
510 std::memcpy(ICB, J0, 16);
514 std::vector<uint8_t> output(plaintext_size +
TAG_SIZE);
515 gctr(cipher_, ICB, plaintext, plaintext_size, output.data());
527 if (aad !=
nullptr && aad_size > 0) {
528 X = ghash_update(X, aad, aad_size);
532 if (plaintext_size > 0) {
533 X = ghash_update(X, output.data(), plaintext_size);
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;
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));
545 Block128 len_b = load_block(len_block);
546 X = xor_blocks(X, len_b);
547 X = gf128_mul_ct(H_table_, X);
553 gctr(cipher_, J0, S, 16, tag);
556 std::memcpy(output.data() + plaintext_size, tag,
TAG_SIZE);
593 const uint8_t* ciphertext_with_tag,
size_t total_size,
595 const uint8_t* aad =
nullptr,
size_t aad_size = 0)
const {
597 using namespace detail::gcm;
602 "AES-GCM: input too short for authentication tag"};
607 "AES-GCM: ciphertext exceeds NIST SP 800-38D maximum"};
612 "AES-GCM: AAD exceeds NIST SP 800-38D §5.2.1.1 maximum"};
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;
627 if (aad !=
nullptr && aad_size > 0) {
628 X = ghash_update(X, aad, aad_size);
632 if (ciphertext_size > 0) {
633 X = ghash_update(X, ciphertext, ciphertext_size);
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));
644 Block128 len_b = load_block(len_block);
645 X = xor_blocks(X, len_b);
646 X = gf128_mul_ct(H_table_, X);
651 uint8_t expected_tag[16];
652 gctr(cipher_, J0, S, 16, expected_tag);
656 for (
int i = 0; i < static_cast<int>(
TAG_SIZE); ++i) {
657 diff |= received_tag[i] ^ expected_tag[i];
661 "AES-GCM: authentication tag mismatch"};
666 std::memcpy(ICB, J0, 16);
669 std::vector<uint8_t> plaintext(ciphertext_size);
670 gctr(cipher_, ICB, ciphertext, ciphertext_size, plaintext.data());
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;
692 Block128 iv_block = load_block(iv);
693 X = xor_blocks(X, iv_block);
694 X = gf128_mul_ct(H_table_, X);
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));
710 detail::gcm::Block128 ghash_update(
711 detail::gcm::Block128 X,
712 const uint8_t* data,
size_t data_size)
const {
714 using namespace detail::gcm;
716 size_t full_blocks = data_size / 16;
717 for (
size_t i = 0; i < full_blocks; ++i) {
724 size_t remainder = data_size % 16;
726 uint8_t padded[16] = {};
727 std::memcpy(padded, data + full_blocks * 16, remainder);
AES-256 block cipher implementation (FIPS-197).
AES-256 block cipher (FIPS-197).
void encrypt_block(uint8_t block[BLOCK_SIZE]) const
Encrypt a single 16-byte block in-place (FIPS-197 Section 5.1).
AES-256 in Galois/Counter Mode (GCM) as specified in NIST SP 800-38D.
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).
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).
static constexpr uint64_t MAX_AAD_BYTES
NIST SP 800-38D §5.2.1.1: AAD length limit is 2^64-1 bits.
static constexpr size_t KEY_SIZE
AES-256 key size in bytes.
AesGcm(const uint8_t key[KEY_SIZE])
Initialize with a 32-byte key.
static constexpr size_t TAG_SIZE
Authentication tag size in bytes (128 bits).
static constexpr uint64_t MAX_GCM_PLAINTEXT
Maximum plaintext size for a single GCM invocation (NIST SP 800-38D §5.2.1.1).
static constexpr size_t IV_SIZE
Standard GCM nonce size in bytes (96 bits).
void set_iv_size(size_t size)
Set the expected IV size.
size_t iv_size() const
Get the current IV size (12 or 16 bytes).
A lightweight result type that holds either a success value of type T or an Error.
uint8_t rev4(uint8_t n)
Bit-reverse a 4-bit nibble value.
Block128 load_block(const uint8_t src[16])
Load a 16-byte array into a Block128 (big-endian byte order).
void store_block(uint8_t dst[16], const Block128 &b)
Store a Block128 into a 16-byte array (big-endian byte order).
Block128 ghash(const Block128 &H, const uint8_t *data, size_t data_size)
GHASH: Compute the GHASH function over data using hash subkey H.
Block128 gf128_double(const Block128 &V)
"Doubling" in GF(2^128): multiply by x (shift right by 1 in GCM bit ordering).
uint64_t ct_reduce4(uint8_t index)
Constant-time 4-bit reduction table lookup.
Block128 xor_blocks(const Block128 &a, const Block128 &b)
XOR two Block128 values.
void inc32(uint8_t counter[16])
Increment the rightmost 32 bits of a 16-byte counter block (big-endian).
Block128 gf128_mul(const Block128 &X, const Block128 &Y)
Multiply two elements in GF(2^128) using the schoolbook algorithm.
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.
Block128 ct_table_lookup(const Block128 table[16], uint8_t index)
Constant-time 16-entry Block128 table lookup.
Block128 gf128_mul_ct(const GHashTable &table, const Block128 &X)
Constant-time GF(2^128) multiplication using the 4-bit precomputed table.
@ 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.
128-bit value stored as two big-endian uint64_t halves.
Block128()
Default constructor – zero-initializes both halves.
uint64_t hi
Bits 127..64 (most significant half).
uint64_t lo
Bits 63..0 (least significant half).
Block128(uint64_t h, uint64_t l)
Construct from explicit high and low halves.
4-bit precomputed table for constant-time GHASH multiplication.
void init(const Block128 &H)
Precompute the 16-entry multiplication table from hash subkey H.