golang/go · critical

cipher: message authentication failed

Error message

cipher: message authentication failed

What it means

Returned by GCM.Open (errOpen) when authentication fails: the GCM tag computed over ciphertext+associated-data does not match the appended tag, or when the ciphertext is shorter than tagSize / exceeds the GCM maximum. This is the canonical AEAD authentication failure: either the key, nonce, ciphertext, or associated data differs from what was used to Seal.

Source

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

		panic("crypto/cipher: incorrect GCM nonce size")
	}
	if uint64(len(plaintext)) > uint64((1<<32)-2)*gcmBlockSize {
		panic("crypto/cipher: message too large for GCM")
	}

	ret, out := sliceForAppend(dst, len(plaintext)+g.tagSize)
	if alias.InexactOverlap(out, plaintext) {
		panic("crypto/cipher: invalid buffer overlap of output and input")
	}
	if alias.AnyOverlap(out, data) {
		panic("crypto/cipher: invalid buffer overlap of output and additional data")
	}

	seal(out, g, nonce, plaintext, data)
	return ret
}

var errOpen = errors.New("cipher: message authentication failed")

func (g *GCM) Open(dst, nonce, ciphertext, data []byte) ([]byte, error) {
	if len(nonce) != g.nonceSize {
		panic("crypto/cipher: incorrect nonce length given to GCM")
	}
	// Sanity check to prevent the authentication from always succeeding if an
	// implementation leaves tagSize uninitialized, for example.
	if g.tagSize < gcmMinimumTagSize {
		panic("crypto/cipher: incorrect GCM tag size")
	}

	if len(ciphertext) < g.tagSize {
		return nil, errOpen
	}
	if uint64(len(ciphertext)) > uint64((1<<32)-2)*gcmBlockSize+uint64(g.tagSize) {
		return nil, errOpen
	}

View on GitHub (pinned to b6b368adc5)

Solutions

  1. Verify the key, nonce, and additional data byte-for-byte match what Seal used; log nonce and AAD lengths to spot mismatches.
  2. Ensure the ciphertext includes the trailing tag and was not truncated in transit.
  3. If key rotation is the cause, try decryption against recent keys in order.
  4. Treat errOpen as unauthenticated and never disclose which field was wrong (constant-time).

Example fix

// before
plaintext, err := aead.Open(nil, nonce, ciphertext, nil)
if err != nil { panic(err) } // err == errOpen
// after
plaintext, err := aead.Open(nil, nonce, ciphertext, aad) // pass the SAME aad used in Seal
if err != nil {
    return errors.New("decryption failed: authentication tag mismatch")
}
Defensive patterns

Strategy: try-catch

Validate before calling

func looksAuthentic(ciphertext []byte, tagSize int) bool {
    return len(ciphertext) >= tagSize
}
// stronger: verify lengths and AAD before calling Open, but the tag itself
// can only be verified by Open — there is no pre-check that substitutes for it.

Type guard

// n/a

Try / catch

plaintext, err := aead.Open(nil, nonce, ciphertext, aad)
if err != nil {
    // err is errOpen: treat as unauthenticated; do not partially use plaintext
    return errors.New("decryption failed")
}

Prevention

When it happens

Trigger: Calling aead.Open(dst, nonce, ciphertext, data) with the wrong key, wrong/rotated nonce, tampered ciphertext, reordered/truncated associated data, or ciphertext shorter than g.tagSize.

Common situations: Nonce reuse or rotation out of sync; key rotation where the decryptor still holds the old key; network truncation dropping the tag or trailing bytes; associated-data mismatch (e.g. AAD field changed between seal and open); ciphertext from a different AEAD/algorithm fed into GCM.

Understand the failure class

Related errors


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