Skip to content
Sonr Crypto
Esc
navigateopen⌘Jpreview
On this page

Randomized AEAD (AES-256-GCM)

The aead package — AES-256-GCM with a self-generated nonce prepended to every ciphertext, plus the exact key size, tag size, and wire layout.

github.com/sonr-io/crypto/aead is a thin, opinionated wrapper over the standard library’s crypto/cipher.NewGCM. It exists to remove the two decisions people get wrong with raw AES-GCM: it fixes the key size at AES-256 and it generates and transports the nonce for you. The package doc comment cites NIST SP 800-38D.

There is exactly one type, AESGCMCipher, and one constructor. There is no AES-128 mode, no key-unwrapping helper, no streaming interface, and no algorithm identifier on the wire.

When to use it

Reach for aead whenever you have a 32-byte symmetric key and some bytes to protect: session payloads, encrypted records, wrapped blobs, anything where you want confidentiality plus integrity and you can afford a fresh random nonce per message.

Do not use it when:

  • You need the ciphertext to be a stable function of the plaintext (e.g. an encrypted database column you still have to query by equality). Use daed instead.
  • You will encrypt an enormous number of messages under a single key. A random 96-bit nonce is subject to the birthday bound, so nonce collisions become non-negligible after roughly 2^32 messages; rotate keys long before that.
  • You need to encrypt a stream too large to hold in memory. Encrypt and Decrypt are one-shot over full slices.

Constants

All three constants are plain int literals in aes_gcm.go.

Constant Value Meaning
aead.NonceSize 12 96-bit GCM nonce — the size GCM is fastest with, and the only size accepted
aead.TagSize 16 128-bit GCM authentication tag
aead.KeySize 32 AES-256 key size, and the only accepted key length

Ciphertext layout

This is the single most important fact about the package:

Encrypt / EncryptWithNonce output:
┌────────────────┬──────────────────────────┬──────────────────┐
│ nonce 12 bytes │ ciphertext len(plaintext)│ GCM tag 16 bytes │
└────────────────┴──────────────────────────┴──────────────────┘
                 └── produced by gcm.Seal(nil, nonce, pt, aad) ┘

Encrypt generates the nonce itself from crypto/rand and prepends it to the sealed output. So:

  • len(output) == NonceSize + len(plaintext) + TagSize — the test asserts exactly this.
  • Decrypt expects that same layout. It slices data[:12] as the nonce and passes data[12:] (ciphertext and tag) to gcm.Open. You never manage nonces yourself, and you never store them separately.
  • The minimum valid ciphertext is NonceSize + TagSize = 28 bytes (an empty plaintext). Shorter input returns invalid ciphertext length: minimum 28 bytes required before any crypto runs.

The AAD is not part of the output. Whatever you pass as aad must be reproducible at decrypt time from context you already have — a record ID, a version tag, a tenant name.

Usage

Grounded in aead/aes_gcm_test.go (TestAESGCMEncryptDecrypt):

package main

import (
	"crypto/rand"
	"fmt"

	"github.com/sonr-io/crypto/aead"
)

func main() {
	// AES-256 key: exactly aead.KeySize bytes.
	key := make([]byte, aead.KeySize)
	if _, err := rand.Read(key); err != nil {
		panic(err)
	}

	cipher, err := aead.NewAESGCM(key)
	if err != nil {
		panic(err)
	}

	plaintext := []byte("secret data")
	aad := []byte("additional auth data") // authenticated, not encrypted, not stored

	// Encrypt returns nonce || ciphertext || tag.
	ct, err := cipher.Encrypt(plaintext, aad)
	if err != nil {
		panic(err)
	}
	fmt.Println(len(ct) == aead.NonceSize+len(plaintext)+aead.TagSize) // true

	// Decrypt takes that whole blob back, plus the identical AAD.
	pt, err := cipher.Decrypt(ct, aad)
	if err != nil {
		panic(err) // "decryption and authentication failed: ..."
	}
	fmt.Printf("%s\n", pt) // secret data
}

One AESGCMCipher value can be reused for many messages — the underlying cipher.AEAD is stateless and safe for concurrent use, and each Encrypt draws a fresh nonce.

API reference

PropType
NewAESGCM(key []byte)(*AESGCMCipher, error)

Constructor. Errors unless len(key) == 32. Wraps aes.NewCipher then cipher.NewGCM.

Type(*AESGCMCipher, error)
Encrypt(plaintext, aad []byte)([]byte, error)

Draws a fresh 12-byte nonce from crypto/rand, seals, and returns nonce || ciphertext || tag. Accepts empty plaintext and nil aad.

Type([]byte, error)
Decrypt(data, aad []byte)([]byte, error)

Expects nonce || ciphertext || tag. Errors if len(data) < 28, or if the tag or AAD does not verify.

Type([]byte, error)
EncryptWithNonce(plaintext, aad, nonce []byte)?([]byte, error)

Same output layout, but with a caller-supplied nonce. Errors unless len(nonce) == 12. See the danger note below.

Type([]byte, error)
GetNonceSize()?int

Always returns the NonceSize constant, 12.

Typeint
GetTagSize()?int

Always returns the TagSize constant, 16.

Typeint

AESGCMCipher has exactly one field, an unexported gcm cipher.AEAD. There is nothing to configure and nothing to inspect.

Caveats

Last updated on September 2, 2026

Was this page helpful?