golang/go · error

crypto/cipher: SetNoncePrefixAndMask called twice or after f

Error message

crypto/cipher: SetNoncePrefixAndMask called twice or after first Seal

What it means

Returned by GCMWithXORCounterNonce.SetNoncePrefixAndMask when g.ready is already true, i.e. the method is called a second time, or called after the first Seal (which lazily sets ready if SetNoncePrefixAndMask was skipped). The prefix and mask are immutable for the lifetime of the AEAD so that Seal can enforce a strictly-increasing counter.

Source

Thrown at src/crypto/internal/fips140/aes/gcm/gcm_nonces.go:234

	mask   uint64
	next   uint64
}

// SetNoncePrefixAndMask sets the fixed prefix and XOR mask for the nonces used
// in Seal. It must be called before the first call to Seal.
//
// The first 32 bits of nonce are used as the fixed prefix, and the last 64 bits
// are used as the XOR mask.
//
// Note that Seal expects the nonce to be already XOR'd with the mask. The mask
// is provided here only to allow Seal to enforce that the counter is strictly
// increasing.
func (g *GCMWithXORCounterNonce) SetNoncePrefixAndMask(nonce []byte) error {
	if len(nonce) != gcmStandardNonceSize {
		return errors.New("crypto/cipher: incorrect nonce length given to SetNoncePrefixAndMask")
	}
	if g.ready {
		return errors.New("crypto/cipher: SetNoncePrefixAndMask called twice or after first Seal")
	}
	g.prefix = byteorder.BEUint32(nonce[:4])
	g.mask = byteorder.BEUint64(nonce[4:])
	g.ready = true
	return nil
}

func (g *GCMWithXORCounterNonce) NonceSize() int { return gcmStandardNonceSize }

func (g *GCMWithXORCounterNonce) Overhead() int { return gcmTagSize }

// Seal implements the [cipher.AEAD] interface, checking that the nonce prefix
// is stable and that the counter is strictly increasing.
//
// It is not safe for concurrent use.
func (g *GCMWithXORCounterNonce) Seal(dst, nonce, plaintext, data []byte) []byte {
	if len(nonce) != gcmStandardNonceSize {
		panic("crypto/cipher: incorrect nonce length given to GCM")

View on GitHub (pinned to b6b368adc5)

Solutions

  1. Call SetNoncePrefixAndMask exactly once, before any Seal; prefer NewGCMForQUIC which does both steps atomically.
  2. Do not reuse/reset the same GCMWithXORCounterNonce across key rotations; construct a new one.
  3. If you must change prefix/mask, allocate a fresh GCMWithXORCounterNonce via NewGCMForQUIC.

Example fix

// before
g, _ := gcm.NewGCMForQUIC(block, iv)
g.SetNoncePrefixAndMask(iv) // duplicate -> error
// after
g, _ := gcm.NewGCMForQUIC(block, iv) // sets prefix+mask exactly once
// ... use g.Seal(...) directly
Defensive patterns

Strategy: validation

Validate before calling

// SetNoncePrefixAndMask must be called at most once and only before Seal.
// Track readiness in the caller and refuse to call again.
type nonceInit struct{ done bool }
func (n *nonceInit) init(g *gcm.GCMWithXORCounterNonce, iv []byte) error {
    if n.done { return errors.New("nonce already initialized") }
    n.done = true
    return g.SetNoncePrefixAndMask(iv)
}

Type guard

// n/a

Try / catch

if err := g.SetNoncePrefixAndMask(iv); err != nil {
    if strings.Contains(err.Error(), "called twice") {
        // lifecycle bug: rebuild the AEAD via NewGCMForQUIC instead
    }
    return err
}

Prevention

When it happens

Trigger: Calling SetNoncePrefixAndMask twice; calling it after Seal has already been invoked (Seal sets g.ready=true on its first call if it was false); constructing via NewGCMForQUIC (which calls it once) and then calling it again externally.

Common situations: Re-initializing the AEAD per-packet instead of constructing once and reusing; copy-paste wiring that calls both NewGCMForQUIC and an explicit SetNoncePrefixAndMask; lifecycle bug where the same GCMWithXORCounterNonce is reset rather than recreated.

Related errors


AI-assisted analysis of golang/go@b6b368adc5 (2026-08-12). Data as JSON: /api/errors/7385c2d0222c906d. Report an issue: GitHub.