# How fabrica-util's AES-GCM Implementation Ensures Data Integrity and Confidentiality

> Explore how fabrica-util's AES-GCM implementation guarantees data integrity and confidentiality in game communication through AEAD and robust verification.

- Repository: [Pantheon/fabrica-util](https://github.com/go-pantheon/fabrica-util)
- Tags: deep-dive
- Published: 2026-03-02

---

**fabrica-util's AES-GCM implementation ensures data integrity and confidentiality by using authenticated encryption with associated data (AEAD), randomly generated nonces for each message, and strict authentication tag verification that rejects any tampered ciphertext.**

The `go-pantheon/fabrica-util` repository provides a production-ready cryptographic utility specifically designed for game servers and clients. Its [`security/aes/aes.go`](https://github.com/go-pantheon/fabrica-util/blob/main/security/aes/aes.go) file implements an **AES-GCM** codec that simultaneously protects message content from eavesdroppers and detects any unauthorized modifications, making it ideal for securing real-time game communication protocols.

## Confidentiality Through AES-GCM Encryption

The implementation achieves confidentiality by encrypting plaintext using the AES block cipher in Galois/Counter Mode (GCM). In [`security/aes/aes.go`](https://github.com/go-pantheon/fabrica-util/blob/main/security/aes/aes.go), the `Encrypt` method generates a cryptographically secure random nonce via `io.ReadFull(rand.Reader, nonce)`, ensuring each encryption operation uses unique initialization vectors.

The `Cipher` struct wraps an AES-GCM instance created through `cipher.NewGCM`. When `Encrypt` is called, it invokes `c.block.Seal(nil, nonce, data, nil)`, which produces ciphertext that is computationally infeasible to decrypt without the original key. The nonce is prepended to the sealed output, resulting in a payload format of `nonce‖ciphertext‖tag` that travels securely over the network.

## Integrity Verification via Authentication Tags

Data integrity is enforced through GCM's built-in authentication mechanism. Every encryption automatically appends a 16-byte authentication tag to the ciphertext. During decryption, the `Decrypt` method extracts the nonce from the front of the data and calls `c.block.Open(nil, nonce, ciphertext, nil)`.

If any bit of the ciphertext, nonce, or authentication tag has been altered during transmission, `Open` returns an error. The implementation catches this in [`security/aes/aes.go`](https://github.com/go-pantheon/fabrica-util/blob/main/security/aes/aes.go) and wraps it using `errors.Wrap(err, "failed to decrypt data")`, immediately signaling to the caller that the message integrity has been compromised and the data should be discarded.

## Replay Protection with Random Nonce Generation

The implementation prevents replay attacks through strict nonce uniqueness guarantees. Each call to `Encrypt` creates a fresh nonce sized according to `c.block.NonceSize()`—standardly 12 bytes for AES-GCM. Because these nonces are randomly generated using `crypto/rand`, the probability of nonce reuse is negligible, ensuring that identical plaintexts produce completely different ciphertexts.

During decryption, the nonce is extracted from the first `NonceSize()` bytes of the received data (`nonce := data[:c.block.NonceSize()]`), then used to initialize the decryption context. This per-message uniqueness prevents attackers from replaying captured packets to spoof game state or actions.

## Implementation Details in security/aes/aes.go

### Key Validation and Initialization

The `NewAESCipher` function enforces strict key length requirements, accepting only 16, 24, or 32-byte keys for AES-128, AES-192, and AES-256 respectively. This validation occurs before the AEAD instance is constructed via `cipher.NewGCM(block)`, ensuring only valid AES keys initialize the cipher.

### Handling Empty Payloads

The API includes `EncryptAllowEmpty` and `DecryptAllowEmpty` variants that bypass the standard non-empty input checks. These convenience methods allow game protocols to transmit zero-length payloads—such as keepalive packets—without compromising the security model or triggering unnecessary errors.

### Error Handling Strategy

All cryptographic failures utilize the repository's custom `errors` package, preserving full stack traces while providing clear failure messages to calling code. This allows game servers to log detailed security events while maintaining clean error boundaries between the codec and network layers.

## Using the Cipher in Game Communication

The following example demonstrates encrypting a JSON game payload and verifying integrity on the receiving side:

```go
package main

import (
    "fmt"
    "log"

    "github.com/go-pantheon/fabrica-util/security/aes"
    "github.com/go-pantheon/fabrica-util/xrand"
)

func main() {
    // Generate a 32-byte random key (AES-256)
    key, _ := xrand.RandAlphaNumString(32)

    // Initialize cipher instances for both client and server
    client, err := aes.NewAESCipher([]byte(key))
    if err != nil {
        log.Fatalf("cipher init: %v", err)
    }
    server, err := aes.NewAESCipher([]byte(key))
    if err != nil {
        log.Fatalf("cipher init: %v", err)
    }

    // Encrypt a game action payload
    payload := []byte(`{"action":"move","x":42,"y":7}`)
    enc, err := client.Encrypt(payload)
    if err != nil {
        log.Fatalf("encrypt: %v", err)
    }

    // Transmit enc over the network...
    // If any byte is altered here, decryption will fail

    // Decrypt and verify integrity
    dec, err := server.Decrypt(enc)
    if err != nil {
        log.Fatalf("decrypt (tampered?): %v", err)
    }

    fmt.Printf("Recovered payload: %s\n", string(dec))
}

```

If an attacker modifies the ciphertext during transit—flipping even a single bit—the `Decrypt` method returns an error because the GCM authentication tag verification fails, protecting the game state from corruption or exploits.

## Summary

- **Confidentiality**: Achieved through AES-GCM encryption with random 12-byte nonces generated via `crypto/rand`
- **Integrity**: Enforced by 16-byte authentication tags that cause `c.block.Open` to error on any tampering
- **Replay Protection**: Guaranteed by unique per-message nonces prepended to ciphertext in [`security/aes/aes.go`](https://github.com/go-pantheon/fabrica-util/blob/main/security/aes/aes.go)
- **Key Safety**: `NewAESCipher` validates 16, 24, or 32-byte keys before initialization
- **Flexibility**: `EncryptAllowEmpty` and `DecryptAllowEmpty` support zero-length protocol payloads

## Frequently Asked Questions

### What key sizes does fabrica-util's AES-GCM implementation support?

The implementation supports standard AES key lengths: 16 bytes (AES-128), 24 bytes (AES-192), and 32 bytes (AES-256). The `NewAESCipher` function in [`security/aes/aes.go`](https://github.com/go-pantheon/fabrica-util/blob/main/security/aes/aes.go) explicitly validates these lengths and returns an error for invalid key sizes, ensuring compatibility with the underlying Go standard library AES implementation.

### How does the implementation prevent replay attacks?

Replay attacks are prevented through cryptographically random nonce generation. Each call to `Encrypt` generates a fresh nonce using `io.ReadFull(rand.Reader, nonce)`, creating unique ciphertexts even for identical plaintexts. Since nonces are never reused with the same key, attackers cannot replay captured messages to spoof game actions.

### What happens if ciphertext is tampered with during transmission?

If any portion of the ciphertext—including the prepended nonce or appended authentication tag—is modified, the `Decrypt` method detects the tampering. During the `c.block.Open` call, GCM tag verification fails and returns an error, which the implementation wraps and returns to the caller. The decrypted plaintext is automatically discarded, preventing corrupted data from reaching game logic.

### Can the cipher handle empty payloads for keepalive messages?

Yes, through the `EncryptAllowEmpty` and `DecryptAllowEmpty` methods. While standard `Encrypt` and `Decrypt` reject empty byte slices, these variants bypass the non-empty checks to support protocols requiring zero-length payloads—such as TCP keepalives or heartbeat signals—without compromising the security model or requiring workarounds.