XAES-256-GCM

Header: #include <cryptopp/xaes_256_gcm.h> Namespace: CryptoPP Since: cryptopp-modern 2025.12

XAES-256-GCM is an extended-nonce variant of AES-256-GCM that enables safe random nonce generation for a virtually unlimited number of messages. It follows the C2SP XAES-256-GCM specification.

Quick Example

#include <cryptopp/xaes_256_gcm.h>
#include <cryptopp/osrng.h>
#include <iostream>

int main() {
    using namespace CryptoPP;

    AutoSeededRandomPool prng;

    // Generate random key and nonce
    SecByteBlock key(32);
    prng.GenerateBlock(key, key.size());

    byte nonce[24];
    prng.GenerateBlock(nonce, sizeof(nonce));

    // Message to encrypt
    std::string plaintext = "Hello, XAES-256-GCM!";
    std::string ciphertext(plaintext.size(), '\0');
    byte tag[16];

    // Encrypt
    XAES_256_GCM::Encryption enc;
    enc.SetKey(key, key.size());
    enc.EncryptAndAuthenticate(
        (byte*)ciphertext.data(), tag, sizeof(tag),
        nonce, static_cast<int>(sizeof(nonce)),
        nullptr, 0,
        (const byte*)plaintext.data(), plaintext.size()
    );

    // Decrypt
    std::string recovered(ciphertext.size(), '\0');
    XAES_256_GCM::Decryption dec;
    dec.SetKey(key, key.size());

    bool valid = dec.DecryptAndVerify(
        (byte*)recovered.data(), tag, sizeof(tag),
        nonce, static_cast<int>(sizeof(nonce)),
        nullptr, 0,
        (const byte*)ciphertext.data(), ciphertext.size()
    );

    if (valid) {
        std::cout << "Decrypted: " << recovered << std::endl;
    }

    return 0;
}

Usage Guidelines

ℹ️

Do:

  • Use XAES-256-GCM when you need safe random nonce generation
  • Generate random 24-byte nonces using AutoSeededRandomPool
  • Always verify the return value of DecryptAndVerify()
  • Use a fresh nonce per message (one-shot APIs handle this automatically; for streaming, call Resynchronize() before each message)

Avoid:

  • Calling Restart() (throws exception to prevent nonce reuse)
  • Using keys not exactly 32 bytes (throws InvalidKeyLength)
  • Using nonces not exactly 24 bytes (throws InvalidArgument)
  • Ignoring authentication failures during decryption

Class: XAES_256_GCM

Extended-nonce AES-256-GCM authenticated encryption with safe random nonce generation.

XAES_256_GCM provides two nested types:

  • XAES_256_GCM::Encryption - for authenticated encryption
  • XAES_256_GCM::Decryption - for authenticated decryption

Constants

Available on XAES_256_GCM::Encryption and XAES_256_GCM::Decryption:

XAES_256_GCM::Encryption::KEY_SIZE  // 32 (256 bits, fixed)
XAES_256_GCM::Encryption::IV_SIZE   // 24 (192 bits, fixed)
XAES_256_GCM::Encryption::TAG_SIZE  // 16 (128 bits, fixed)

Key Features

FeatureValueNotes
Key size32 bytes (256 bits)Fixed, no other sizes supported
Nonce size24 bytes (192 bits)Fixed, enables safe random generation
Tag size16 bytes (128 bits)Fixed
Safe random noncesYes~2^80 messages before collision risk
Overhead+2 AES blocks/messagePlus 1 block cached per key

XAES_256_GCM::Encryption

Authenticated encryption class.

Methods

SetKey()

void SetKey(const byte* key, size_t keyLength,
            const NameValuePairs& params = g_nullNameValuePairs);

Set the encryption key.

Parameters:

  • key - Encryption key (must be exactly 32 bytes)
  • keyLength - Length of key in bytes (must be 32)
  • params - Optional algorithm parameters (typically unused)

Throws: InvalidKeyLength if key is not 32 bytes

Example:

XAES_256_GCM::Encryption enc;
SecByteBlock key(32);

AutoSeededRandomPool prng;
prng.GenerateBlock(key, key.size());

enc.SetKey(key, key.size());

SetKeyWithIV()

void SetKeyWithIV(const byte* key, size_t keyLength,
                  const byte* iv, size_t ivLength = IV_SIZE);

Set key and nonce in a single call.

Parameters:

  • key - Encryption key (32 bytes)
  • keyLength - Length of key (must be 32)
  • iv - Nonce (24 bytes)
  • ivLength - Length of nonce (defaults to 24)

Example:

XAES_256_GCM::Encryption enc;
SecByteBlock key(32);
byte nonce[24];

AutoSeededRandomPool prng;
prng.GenerateBlock(key, key.size());
prng.GenerateBlock(nonce, sizeof(nonce));

enc.SetKeyWithIV(key, key.size(), nonce, sizeof(nonce));

Resynchronize()

void Resynchronize(const byte* iv, int ivLength = -1);

Set a new nonce for the next message. Use this between messages with the same key.

Parameters:

  • iv - New nonce (24 bytes)
  • ivLength - Length of nonce; -1 (default) uses IVSize() which is 24

Throws: InvalidArgument if nonce is not 24 bytes

Example:

// Process multiple messages with same key
XAES_256_GCM::Encryption enc;
enc.SetKey(key, key.size());

// Message 1
byte nonce1[24];
prng.GenerateBlock(nonce1, sizeof(nonce1));
enc.Resynchronize(nonce1);
// ... encrypt message 1 ...

// Message 2 - MUST use fresh nonce
byte nonce2[24];
prng.GenerateBlock(nonce2, sizeof(nonce2));
enc.Resynchronize(nonce2);
// ... encrypt message 2 ...

EncryptAndAuthenticate()

void EncryptAndAuthenticate(byte* ciphertext, byte* mac, size_t macSize,
                            const byte* iv, int ivLength,
                            const byte* aad, size_t aadLength,
                            const byte* message, size_t messageLength);

One-shot encryption with authentication.

Parameters:

  • ciphertext - Output buffer (same size as message)
  • mac - Output authentication tag (16 bytes)
  • macSize - Size of MAC buffer (must be exactly 16)
  • iv - Nonce (24 bytes)
  • ivLength - Nonce length (24)
  • aad - Additional authenticated data (can be NULL if aadLength is 0)
  • aadLength - AAD length
  • message - Plaintext to encrypt (can be NULL if messageLength is 0)
  • messageLength - Plaintext length

Throws: InvalidArgument if macSize is not 16, or if buffers are null with non-zero lengths

Example:

XAES_256_GCM::Encryption enc;
enc.SetKey(key, key.size());

std::string plaintext = "Secret message";
std::string ciphertext(plaintext.size(), '\0');
byte tag[16];

enc.EncryptAndAuthenticate(
    (byte*)ciphertext.data(), tag, sizeof(tag),
    nonce, static_cast<int>(sizeof(nonce)),
    nullptr, 0,  // No AAD
    (const byte*)plaintext.data(), plaintext.size()
);

GetNextIV()

void GetNextIV(RandomNumberGenerator& rng, byte* iv);

Generate a random nonce suitable for use with this cipher.

Parameters:

  • rng - Random number generator
  • iv - Output buffer for 24-byte nonce

Example:

AutoSeededRandomPool prng;
XAES_256_GCM::Encryption enc;
enc.SetKey(key, key.size());

byte nonce[24];
enc.GetNextIV(prng, nonce);
enc.Resynchronize(nonce);  // uses IVSize() internally

XAES_256_GCM::Decryption

Authenticated decryption class.

Methods

DecryptAndVerify()

bool DecryptAndVerify(byte* message,
                      const byte* mac, size_t macSize,
                      const byte* iv, int ivLength,
                      const byte* aad, size_t aadLength,
                      const byte* ciphertext, size_t ciphertextLength);

One-shot decryption with authentication verification.

Parameters:

  • message - Output buffer for plaintext
  • mac - Authentication tag to verify (16 bytes)
  • macSize - Size of MAC (must be exactly 16)
  • iv - Nonce (must match encryption)
  • ivLength - Nonce length (24)
  • aad - Additional authenticated data (must match encryption, can be NULL if aadLength is 0)
  • aadLength - AAD length
  • ciphertext - Encrypted data (can be NULL if ciphertextLength is 0)
  • ciphertextLength - Ciphertext length

Returns: true if authentication succeeded, false if verification failed

Throws: InvalidArgument if macSize is not 16, or if buffers are null with non-zero lengths

Important: Always check the return value. If false, the message has been tampered with and you must discard the plaintext.

Example:

XAES_256_GCM::Decryption dec;
dec.SetKey(key, key.size());

std::string plaintext(ciphertext.size(), '\0');
bool valid = dec.DecryptAndVerify(
    (byte*)plaintext.data(),
    tag, sizeof(tag),
    nonce, static_cast<int>(sizeof(nonce)),
    nullptr, 0,  // No AAD
    (const byte*)ciphertext.data(), ciphertext.size()
);

if (!valid) {
    SecureWipeArray((byte*)plaintext.data(), plaintext.size());
    throw std::runtime_error("Authentication failed!");
}

Streaming Interface

For large messages or incremental processing.

⚠️

Streaming Misuse Protection: The streaming interface enforces that Resynchronize() must be called before each message. After TruncatedFinal() or TruncatedVerify() completes, attempting to call Update() or ProcessData() without first calling Resynchronize() will throw BadState. This prevents accidental nonce reuse.

Fixed Tag Size: TruncatedFinal() and TruncatedVerify() require exactly 16 bytes for the tag (throws InvalidArgument otherwise). This matches the scheme’s fixed-tag design.

Streaming Encryption

#include <cryptopp/xaes_256_gcm.h>
#include <cryptopp/osrng.h>

void streamingEncrypt() {
    using namespace CryptoPP;

    AutoSeededRandomPool prng;
    SecByteBlock key(32);
    prng.GenerateBlock(key, key.size());

    byte nonce[24];
    prng.GenerateBlock(nonce, sizeof(nonce));

    XAES_256_GCM::Encryption enc;
    enc.SetKey(key, key.size());
    enc.Resynchronize(nonce);

    // Optional: Process AAD first
    std::string aad = "header-data";
    enc.Update((const byte*)aad.data(), aad.size());

    // Process plaintext in chunks
    std::string chunk1 = "First chunk. ";
    std::string chunk2 = "Second chunk.";

    std::string ct1(chunk1.size(), '\0');
    std::string ct2(chunk2.size(), '\0');

    enc.ProcessData((byte*)ct1.data(),
                    (const byte*)chunk1.data(), chunk1.size());
    enc.ProcessData((byte*)ct2.data(),
                    (const byte*)chunk2.data(), chunk2.size());

    // Finalize and get the tag
    byte tag[16];
    enc.TruncatedFinal(tag, sizeof(tag));
}

Streaming Decryption

void streamingDecrypt(const SecByteBlock& key, const byte* nonce,
                      const std::string& aad,
                      const std::string& ciphertext, const byte* tag) {
    using namespace CryptoPP;

    XAES_256_GCM::Decryption dec;
    dec.SetKey(key, key.size());
    dec.Resynchronize(nonce, 24);

    // Process AAD (must match encryption)
    dec.Update((const byte*)aad.data(), aad.size());

    // Decrypt data
    std::string plaintext(ciphertext.size(), '\0');
    dec.ProcessData((byte*)plaintext.data(),
                    (const byte*)ciphertext.data(), ciphertext.size());

    // Verify the tag
    if (!dec.TruncatedVerify(tag, 16)) {
        SecureWipeArray((byte*)plaintext.data(), plaintext.size());
        throw std::runtime_error("Authentication failed");
    }
}

Additional Authenticated Data (AAD)

AAD provides authenticated (but not encrypted) metadata bound to the ciphertext.

void encryptWithAAD() {
    using namespace CryptoPP;

    AutoSeededRandomPool prng;
    SecByteBlock key(32);
    prng.GenerateBlock(key, key.size());

    byte nonce[24];
    prng.GenerateBlock(nonce, sizeof(nonce));

    // Message and AAD
    std::string plaintext = "Secret message";
    std::string aad = "message-id:12345;timestamp:2025-01-15";

    std::string ciphertext(plaintext.size(), '\0');
    byte tag[16];

    // Encrypt with AAD
    XAES_256_GCM::Encryption enc;
    enc.SetKey(key, key.size());
    enc.EncryptAndAuthenticate(
        (byte*)ciphertext.data(), tag, sizeof(tag),
        nonce, static_cast<int>(sizeof(nonce)),
        (const byte*)aad.data(), aad.size(),
        (const byte*)plaintext.data(), plaintext.size()
    );
}

void decryptWithAAD(const SecByteBlock& key, const byte* nonce,
                    const std::string& aad, const std::string& ciphertext,
                    const byte* tag) {
    using namespace CryptoPP;

    std::string recovered(ciphertext.size(), '\0');

    XAES_256_GCM::Decryption dec;
    dec.SetKey(key, key.size());

    // AAD must match exactly for verification to succeed
    bool valid = dec.DecryptAndVerify(
        (byte*)recovered.data(), tag, 16,
        nonce, 24,
        (const byte*)aad.data(), aad.size(),
        (const byte*)ciphertext.data(), ciphertext.size()
    );

    if (!valid) {
        SecureWipeArray((byte*)recovered.data(), recovered.size());
        throw std::runtime_error("Authentication failed - AAD or ciphertext tampered");
    }
}

Complete Example

A full encryption/decryption round-trip with best practices.

#include <cryptopp/xaes_256_gcm.h>
#include <cryptopp/osrng.h>
#include <cryptopp/hex.h>
#include <iostream>
#include <stdexcept>

using namespace CryptoPP;

struct EncryptedMessage {
    SecByteBlock nonce;
    std::string ciphertext;
    SecByteBlock tag;

    EncryptedMessage() : nonce(24), tag(16) {}
};

EncryptedMessage Encrypt(const SecByteBlock& key,
                         const std::string& plaintext,
                         const std::string& aad = "") {
    AutoSeededRandomPool prng;
    EncryptedMessage msg;

    // Generate random nonce
    prng.GenerateBlock(msg.nonce, msg.nonce.size());

    // Prepare ciphertext buffer
    msg.ciphertext.resize(plaintext.size());

    // Encrypt
    XAES_256_GCM::Encryption enc;
    enc.SetKey(key, key.size());
    enc.EncryptAndAuthenticate(
        (byte*)msg.ciphertext.data(), msg.tag, msg.tag.size(),
        msg.nonce, (int)msg.nonce.size(),
        aad.empty() ? nullptr : (const byte*)aad.data(), aad.size(),
        (const byte*)plaintext.data(), plaintext.size()
    );

    return msg;
}

std::string Decrypt(const SecByteBlock& key,
                    const EncryptedMessage& msg,
                    const std::string& aad = "") {
    std::string plaintext(msg.ciphertext.size(), '\0');

    XAES_256_GCM::Decryption dec;
    dec.SetKey(key, key.size());

    bool valid = dec.DecryptAndVerify(
        (byte*)plaintext.data(), msg.tag, msg.tag.size(),
        msg.nonce, (int)msg.nonce.size(),
        aad.empty() ? nullptr : (const byte*)aad.data(), aad.size(),
        (const byte*)msg.ciphertext.data(), msg.ciphertext.size()
    );

    if (!valid) {
        SecureWipeArray((byte*)plaintext.data(), plaintext.size());
        throw std::runtime_error("XAES-256-GCM: Authentication failed");
    }

    return plaintext;
}

int main() {
    try {
        AutoSeededRandomPool prng;

        // Generate a random key
        SecByteBlock key(32);
        prng.GenerateBlock(key, key.size());

        std::string original = "This is a secret message using XAES-256-GCM!";
        std::string aad = "user-id:alice;session:xyz123";

        std::cout << "Original:  " << original << std::endl;

        // Encrypt
        EncryptedMessage encrypted = Encrypt(key, original, aad);

        // Print hex values
        std::string nonceHex, ctHex, tagHex;
        StringSource(encrypted.nonce, encrypted.nonce.size(), true,
            new HexEncoder(new StringSink(nonceHex)));
        StringSource((const byte*)encrypted.ciphertext.data(),
            encrypted.ciphertext.size(), true,
            new HexEncoder(new StringSink(ctHex)));
        StringSource(encrypted.tag, encrypted.tag.size(), true,
            new HexEncoder(new StringSink(tagHex)));

        std::cout << "Nonce:     " << nonceHex << std::endl;
        std::cout << "Ciphertext:" << ctHex << std::endl;
        std::cout << "Tag:       " << tagHex << std::endl;

        // Decrypt
        std::string decrypted = Decrypt(key, encrypted, aad);
        std::cout << "Decrypted: " << decrypted << std::endl;

        if (original == decrypted)
            std::cout << "\nSuccess! Messages match." << std::endl;

        return 0;
    }
    catch (const Exception& e) {
        std::cerr << "Crypto++ error: " << e.what() << std::endl;
        return 1;
    }
}

Error Handling

Invalid Key Length

void handleInvalidKey() {
    try {
        XAES_256_GCM::Encryption enc;

        // Wrong key size - must be exactly 32 bytes
        SecByteBlock shortKey(16);
        enc.SetKey(shortKey, shortKey.size());  // Throws!
    }
    catch (const InvalidKeyLength& e) {
        std::cerr << "Key error: " << e.what() << std::endl;
    }
}

Invalid Nonce Length

void handleInvalidNonce() {
    using namespace CryptoPP;

    AutoSeededRandomPool prng;
    SecByteBlock key(32);
    prng.GenerateBlock(key, key.size());

    try {
        XAES_256_GCM::Encryption enc;
        enc.SetKey(key, key.size());

        // Wrong nonce size - must be exactly 24 bytes
        byte shortNonce[12];
        enc.Resynchronize(shortNonce, sizeof(shortNonce));  // Throws!
    }
    catch (const InvalidArgument& e) {
        std::cerr << "Nonce error: " << e.what() << std::endl;
    }
}

Authentication Failure

void handleAuthFailure() {
    using namespace CryptoPP;

    AutoSeededRandomPool prng;
    SecByteBlock key(32);
    prng.GenerateBlock(key, key.size());

    byte nonce[24];
    prng.GenerateBlock(nonce, sizeof(nonce));

    // Encrypt
    std::string plaintext = "Original message";
    std::string ciphertext(plaintext.size(), '\0');
    byte tag[16];

    XAES_256_GCM::Encryption enc;
    enc.SetKey(key, key.size());
    enc.EncryptAndAuthenticate(
        (byte*)ciphertext.data(), tag, sizeof(tag),
        nonce, static_cast<int>(sizeof(nonce)), nullptr, 0,
        (const byte*)plaintext.data(), plaintext.size()
    );

    // Tamper with ciphertext
    ciphertext[0] ^= 0x01;

    // Decrypt - will fail verification
    std::string recovered(ciphertext.size(), '\0');
    XAES_256_GCM::Decryption dec;
    dec.SetKey(key, key.size());

    bool valid = dec.DecryptAndVerify(
        (byte*)recovered.data(), tag, sizeof(tag),
        nonce, static_cast<int>(sizeof(nonce)), nullptr, 0,
        (const byte*)ciphertext.data(), ciphertext.size()
    );

    if (!valid) {
        // IMPORTANT: Do not use recovered data!
        SecureWipeArray((byte*)recovered.data(), recovered.size());
        std::cerr << "Authentication failed - data was tampered!" << std::endl;
    }
}

Security

Quick Summary

AspectValueWhy it matters
Key size256 bits (fixed)Full AES-256 security
Nonce size192 bits (fixed)Safe random generation
Tag length128 bits (fixed)Strong authenticity
Message limit~2^80 per keyBirthday bound on 192-bit nonce
Key derivationNIST SP 800-108r1CMAC-based, 2 AES blocks/message + 1 cached/key

Security best practices:

  • Generate a random 24-byte nonce for every encryption; the large nonce space makes collisions negligible
  • Always verify the return value of DecryptAndVerify() before using decrypted data
  • Use SecByteBlock for keys to ensure automatic zeroing on destruction
  • Restart() is intentionally disabled (throws exception) to prevent nonce reuse
Detailed Security Properties

Why XAES-256-GCM?

Standard AES-GCM uses a 12-byte (96-bit) nonce, which has a birthday bound of ~2^32 messages before collision risk becomes significant. With random nonces, this limits you to ~4 billion messages per key.

XAES-256-GCM extends the nonce to 24 bytes (192 bits), raising the birthday bound to ~2^80 messages. This makes random nonce generation safe for virtually any application.

Key Derivation

XAES-256-GCM derives a per-message key using NIST SP 800-108r1 (CMAC-based KDF):

  1. Takes the 32-byte master key and the first 12 bytes of the 24-byte nonce
  2. Derives a 32-byte subkey using CMAC (2 AES block encryptions)
  3. Uses the last 12 bytes of the nonce as the GCM IV
  4. Overhead: 2 AES block encryptions per message, plus 1 block (L computation) cached per master key

Restart() Protection

Unlike standard GCM, calling Restart() on XAES-256-GCM throws a BadState exception. This prevents accidental nonce reuse. Always use Resynchronize() with a fresh nonce instead.

Comparison with Standard GCM

FeatureAES-256-GCMXAES-256-GCM
Key size32 bytes32 bytes
Nonce size12 bytes24 bytes
Tag size16 bytes16 bytes
Safe random nonceNo (~2^32 messages)Yes (~2^80 messages)
Per-message overheadNone+2 AES blocks
StandardNIST SP 800-38DC2SP XAES-256-GCM

When to use XAES-256-GCM:

  • Random nonce generation is preferred
  • Managing unique nonces is impractical
  • High message volume per key
  • Distributed systems where nonce coordination is difficult

When to use standard AES-GCM:

  • Counter-based nonces are feasible
  • Maximum performance is critical
  • Interoperability with legacy systems required

Thread Safety

Not thread-safe. Create separate XAES_256_GCM::Encryption and XAES_256_GCM::Decryption objects for each thread.

// WRONG - sharing between threads
XAES_256_GCM::Encryption shared_enc;

// CORRECT - one per thread
void threadFunc() {
    XAES_256_GCM::Encryption enc;  // Thread-local
    // ... use enc ...
}

Exceptions

  • InvalidKeyLength - Key size is not exactly 32 bytes
  • InvalidArgument - Nonce size is not exactly 24 bytes, or tag size is not 16, or null buffer with non-zero length
  • BadState - Restart() was called, or streaming methods called without Resynchronize()

Algorithm Details

  • Base algorithm: AES-256-GCM
  • Key derivation: NIST SP 800-108r1 (CMAC-based)
  • Key size: 256 bits (fixed)
  • Nonce size: 192 bits (24 bytes, fixed)
  • Tag size: 128 bits (16 bytes, fixed)
  • Standard: C2SP XAES-256-GCM

See Also