gofiber/fiber · warning

failed to decrypt ciphertext: %w

Error message

failed to decrypt ciphertext: %w

What it means

Returned by DecryptCookie (utils.go:87-89) when gcm.Open fails to authenticate and decrypt. GCM is authenticated encryption, so this fires on ANY of: wrong key, tampered ciphertext, or wrong associated data (the cookie name is passed as AAD at line 87).

Source

Thrown at middleware/encryptcookie/utils.go:89

	}

	block, err := aes.NewCipher(keyDecoded)
	if err != nil {
		return "", fmt.Errorf("failed to create AES cipher: %w", err)
	}

	gcm, err := cipher.NewGCMWithRandomNonce(block)
	if err != nil {
		return "", fmt.Errorf("failed to create GCM mode: %w", err)
	}

	if len(enc) < gcm.NonceSize()+gcm.Overhead() {
		return "", ErrInvalidEncryptedValue
	}

	plaintext, err := gcm.Open(nil, nil, enc, []byte(name))
	if err != nil {
		return "", fmt.Errorf("failed to decrypt ciphertext: %w", err)
	}

	return string(plaintext), nil
}

// GenerateKey returns a random string of 16, 24, or 32 bytes.
// The length of the key determines the AES encryption algorithm used:
// 16 bytes for AES-128, 24 bytes for AES-192, and 32 bytes for AES-256-GCM.
func GenerateKey(length int) string {
	if length != 16 && length != 24 && length != 32 {
		panic(ErrInvalidKeyLength)
	}

	key := make([]byte, length)

	if _, err := rand.Read(key); err != nil {
		panic(err)
	}

View on GitHub (pinned to 9a4c7e57fe)

Solutions

  1. Verify the cookie NAME passed to DecryptCookie matches the one passed to EncryptCookie (it is used as GCM additional authenticated data).
  2. Verify the KEY is identical across all instances and identical to the one used at encryption time — check env/config drift.
  3. On key rotation, keep the old key around and try DecryptCookie with each until one succeeds, then re-encrypt with the new key.
  4. Treat a decrypt failure as 'invalid session' and re-issue, never as a 500.

Example fix

// before
v, err := encryptcookie.DecryptCookie("sess", c.Cookies("sess"), key)
if err != nil { return err }

// after — AAD (name) must match the encryption name; fall through on failure
v, err := encryptcookie.DecryptCookie("sess", c.Cookies("sess"), key)
if err != nil {
    // either tampered or encrypted under an old key — drop and re-issue
    return c.Next()
}
Defensive patterns

Strategy: try-catch

Try / catch

// try current key, then any prior key for smooth rotation
plaintext, err := tryDecrypt(name, raw, currentKey)
if err != nil {
    for _, old := range oldKeys {
        if p, e := tryDecrypt(name, raw, old); e == nil {
            plaintext = p
            err = nil
            break
        }
    }
}
if err != nil {
    // tampered or unknown key — drop and re-issue
    return c.Next()
}

Prevention

When it happens

Trigger: Decrypting with a different key than was used to encrypt; the cookie name passed to DecryptCookie differs from the name passed to EncryptCookie (AAD mismatch); client/server key rotation mismatch; ciphertext modified by a proxy or attacker; partial cookie truncation that survived the length check at line 83.

Common situations: Rotating the encryption key without a migration window; renaming the cookie between Encrypt and Decrypt calls; deploying a new key to only some instances behind a load balancer; users on old cookies after a key change.

Related errors


AI-assisted analysis of gofiber/fiber@9a4c7e57fe (2026-08-04). Data as JSON: /data/errors/fb6b8c50b2cd5e54.json. Report an issue: GitHub.