{"record":{"id":"4297f4545258ee94","repo":"ory/hydra","slug":"cannot-open-aead","errorCode":null,"errorMessage":"cannot open AEAD","messagePattern":"cannot open AEAD","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"oryx/pagination/keysetpagination_v2/page_token.go","lineNumber":241,"sourceCode":"\t\t}\n\t\treturn errors.WithStack(herodot.ErrInternalServerError().WithReason(\"unable to unmarshal page token\").WithDebug(err.Error()))\n\t}\n\n\treturn nil\n}\n\nfunc openAEAD(key [32]byte, raw []byte) ([]byte, error) {\n\ta, err := aead.New(key)\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, \"cannot create AEAD\")\n\t}\n\tif len(raw) < a.NonceSize() {\n\t\treturn nil, errors.New(\"ciphertext too short\")\n\t}\n\tnonce, ciphertext := raw[:a.NonceSize()], raw[a.NonceSize():]\n\tbs, err := a.Open(nil, nonce, ciphertext, []byte(pageTokenContext))\n\tif err != nil {\n\t\treturn nil, errors.Wrap(err, \"cannot open AEAD\")\n\t}\n\n\treturn bs, nil\n}\n","sourceCodeStart":223,"sourceCodeEnd":246,"githubUrl":"https://github.com/ory/hydra/blob/4174065ffb052799890f7480f5360a877a67ffc1/oryx/pagination/keysetpagination_v2/page_token.go#L223-L246","documentation":"openAEAD calls a.Open to authenticate and decrypt the page token ciphertext with the associated data (pageTokenContext). If authentication fails — wrong key, corrupted/truncated ciphertext, tampered token, or missing/changed associated data — the AEAD refuses to open and this error is returned.","triggerScenarios":"Decrypting a page token whose ciphertext does not verify: token modified in transit, decrypted with a different key than it was sealed with, base64 payload truncated, or pageTokenContext changed between encrypt and decrypt (version mismatch).","commonSituations":"Client holds a page token across a deployment that rotated the encryption key; token copied with truncation from a URL; malicious tampering attempt; upgrading the library changed the AEAD context/algorithm.","solutions":["Treat it as an invalid/expired token: return 400 to the client so it re-issues the first-page query without a token","Verify the same 32-byte key is configured on both encrypting and decrypting sides (check for key rotation drift)","Check the token string is complete — no truncation by URL length limits, proxies, or copy/paste","If the error appeared after a deployment/upgrade, roll back the key or library change and re-test"],"exampleFix":"// server side\nvar pt PageToken\nif err := Decrypt(key, token, &pt); err != nil {\n    return nil, httpErr_BAD_REQUEST(\"invalid page token\") // restart pagination\n}","handlingStrategy":"try-catch","validationCode":"func tokenLooksValid(s string) bool {\n    if s == \"\" { return false }\n    raw, err := base64.URLEncoding.DecodeString(s)\n    return err == nil && len(raw) > 12 // at least nonce-sized\n}","typeGuard":"func isDecryptable(key [32]byte, s string, pt *PageToken) bool {\n    return Decrypt(key, s, pt) == nil\n}","tryCatchPattern":"var pt PageToken\nif err := Decrypt(key, token, &pt); err != nil {\n    if strings.Contains(err.Error(), \"cannot open AEAD\") {\n        // treat as invalid/expired cursor: restart pagination from page 1\n        return listFirstPage(ctx, req)\n    }\n    return err\n}","preventionTips":["Return HTTP 400 (not 500) for AEAD-open failures so clients restart pagination","Never accept user tokens across key rotations without a fallback key","Avoid truncating tokens: raise URL length limits and don't log/copy partial tokens","Keep pageTokenContext stable across library versions within a deployment"],"tags":["crypto","aead","authentication","tampering"],"backgroundTag":"ciphertext-decryption-failed","analyzedSha":"4174065ffb052799890f7480f5360a877a67ffc1","analyzedAt":"2026-09-03T14:52:41.581Z","contentChangedAt":"2026-09-03T14:52:41.581Z","schemaVersion":2},"datasetVersion":"2026-09-10T17:17:09.494Z"}