Waiting for input...
Star SPIKE on GitHub

ADR-0032: Standard 12-Byte Nonce Size for AES-GCM


  • Status: accepted
  • Date: 2024-11-27
  • Tags: Security, Cryptography, AES-GCM

Context and Problem Statement

SPIKE uses AES-GCM for symmetric encryption in several places:

  1. Cipher API (encryption as a service)
  2. Bootstrap verification
  3. Backend storage encryption

GCM technically supports multiple nonce sizes via Go’s cipher.NewGCMWithNonceSize(), but the standard 12-byte (96-bit) nonce is recommended by NIST. Should SPIKE support configurable nonce sizes, or hardcode the standard 12-byte size?

Decision Drivers

  • Security: Follow NIST recommendations and avoid non-standard configurations
  • Performance: 12-byte nonces use a more efficient internal counter mode
  • Interoperability: Clients need to know the expected nonce size
  • Simplicity: Avoid unnecessary configuration complexity

Considered Options

  1. Hardcode 12-byte nonce size - Use the NIST-recommended standard
  2. Make nonce size configurable - Allow runtime or compile-time configuration
  3. Use cipher.NonceSize() everywhere - Query the cipher for its nonce size

Decision

Hardcode the 12-byte nonce size as a constant (crypto.GCMNonceSize) and use it for all nonce validation. Nonce generation already correctly uses c.NonceSize() from the cipher instance.

Rationale

Why 12 bytes is the right choice

Per NIST SP 800-38D (Recommendation for Block Cipher Modes of Operation: Galois/Counter Mode):

  • 96-bit (12-byte) nonces use the efficient counter mode directly
  • Other sizes require an additional GHASH operation, adding overhead
  • 12 bytes provides sufficient uniqueness for random nonce generation
  • This is the default for Go’s cipher.NewGCM()

Why not make it configurable

  • No legitimate use case for non-standard sizes in SPIKE’s context
  • Configuration adds complexity and potential for misconfiguration
  • Non-standard sizes have security implications that users may not understand
  • Protocol versioning (spikeCipherVersion) exists if changes are ever needed

Consistency in generation vs validation

  • Generation: Uses c.NonceSize() - correct, as the cipher knows its size
  • Validation: Uses hardcoded constant - correct, as we enforce the standard

This is not inconsistent. Generation queries the cipher (which returns 12 for standard GCM), while validation enforces that incoming data matches our expected standard.

Consequences

Positive

  • Follows NIST recommendations
  • No configuration complexity
  • Consistent behavior across all SPIKE components
  • Clear documentation via the constant and this ADR

Negative

  • Cannot use non-standard nonce sizes (this is intentional)
  • If Go’s default ever changed (extremely unlikely), we would need updates

Implementation Notes

The constant is defined in internal/crypto/gcm.go:

const GCMNonceSize = 12

This is used by:

  • app/nexus/internal/route/cipher/ - Cipher API validation
  • app/nexus/internal/route/bootstrap/ - Bootstrap verification

If a future protocol version requires different nonce handling, increment spikeCipherVersion and handle accordingly.