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
- Verify the cookie NAME passed to DecryptCookie matches the one passed to EncryptCookie (it is used as GCM additional authenticated data).
- Verify the KEY is identical across all instances and identical to the one used at encryption time — check env/config drift.
- On key rotation, keep the old key around and try DecryptCookie with each until one succeeds, then re-encrypt with the new key.
- 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
- Keep the cookie NAME identical between encrypt and decrypt (it is the GCM AAD).
- Maintain a rolling list of accepted keys during rotation.
- Never expose decrypt failures to the end user — always re-issue.
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
- failed to create GCM mode: %w
- failed to base64-decode value: %w
- decode SHA256 password: invalid length
- encryption key must be 16, 24, or 32 bytes
- failed to base64-decode key: %w
AI-assisted analysis of gofiber/fiber@9a4c7e57fe (2026-08-04).
Data as JSON: /data/errors/fb6b8c50b2cd5e54.json.
Report an issue: GitHub.