golang/go · error

cipher: message authentication failed

Error message

cipher: message authentication failed

What it means

errOpen is returned by aesGCM.Open when the ciphertext cannot be authenticated — either because it is shorter than the GCM tag (16 bytes), exceeds the maximum GCM plaintext bound, or the authentication tag does not verify during decryption. It is the standard 'cipher: message authentication failed' sentinel reused by crypto/cipher.

Source

Thrown at src/crypto/internal/boring/aes.go:349

	if inexactOverlap(dst[n:], plaintext) {
		panic("cipher: invalid buffer overlap")
	}

	outLen := C.size_t(len(plaintext) + gcmTagSize)
	ok := C.EVP_AEAD_CTX_seal_wrapper(
		&g.ctx,
		(*C.uint8_t)(unsafe.Pointer(&dst[n])), outLen,
		base(nonce), C.size_t(len(nonce)),
		base(plaintext), C.size_t(len(plaintext)),
		base(additionalData), C.size_t(len(additionalData)))
	runtime.KeepAlive(g)
	if ok == 0 {
		panic(fail("EVP_AEAD_CTX_seal"))
	}
	return dst[:n+int(outLen)]
}

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

func (g *aesGCM) Open(dst, nonce, ciphertext, additionalData []byte) ([]byte, error) {
	if len(nonce) != gcmStandardNonceSize {
		panic("cipher: incorrect nonce length given to GCM")
	}
	if len(ciphertext) < gcmTagSize {
		return nil, errOpen
	}
	if uint64(len(ciphertext)) > ((1<<32)-2)*aesBlockSize+gcmTagSize {
		return nil, errOpen
	}

	// Make room in dst to append ciphertext without tag.
	n := len(dst)
	for cap(dst) < n+len(ciphertext)-gcmTagSize {
		dst = append(dst[:cap(dst)], 0)
	}
	dst = dst[:n+len(ciphertext)-gcmTagSize]

View on GitHub (pinned to b6b368adc5)

Solutions

  1. Verify the nonce passed to Open matches the one used with Seal (and is unique per message).
  2. Ensure ciphertext was not truncated — check total length before calling Open.
  3. Confirm the key and additionalData (AAD) are identical on both sides.
  4. Treat this error as authentication failure: do not use any plaintext, surface/abort rather than retry.

Example fix

// before
plaintext, err := aead.Open(nil, nonce, cipherText[:15], aad) // shorter than tag -> errOpen

// after
if len(cipherText) < 16 { return errors.New("ciphertext too short") }
plaintext, err := aead.Open(nil, nonce, cipherText, aad)
Defensive patterns

Strategy: try-catch

Validate before calling

func safeOpen(aead cipher.AEAD, nonce, ct, aad []byte) ([]byte, error) {
    if len(ct) < aead.Overhead() {
        return nil, errors.New("ciphertext shorter than tag")
    }
    return aead.Open(nil, nonce, ct, aad)
}

Try / catch

pt, err := aead.Open(nil, nonce, ct, aad)
if err != nil {
    if errors.Is(err, cipher.ErrAuth) || strings.Contains(err.Error(), "message authentication failed") {
        // authentication failure: discard, abort, do not retry unchanged
        return err
    }
    return err
}

Prevention

When it happens

Trigger: Calling AEAD.Open with ciphertext shorter than gcmTagSize (16), ciphertext larger than ((1<<32)-2)*blockSize + tagSize, a wrong/truncated nonce, or any bit-flip in ciphertext/tag that breaks GCM authentication.

Common situations: Wrong nonce reuse/mismatch between seal and open; corrupted ciphertext on the wire; truncated messages; replaying ciphertext with a different key; bit-flip injection attacks being correctly rejected.

Understand the failure class

Related errors


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