{"record":{"id":"d546f6a2ada822a3","repo":"ory/hydra","slug":"malformed-ciphertext-too-short","errorCode":null,"errorMessage":"malformed ciphertext: too short","messagePattern":"malformed ciphertext: too short","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"aead/xchacha20.go","lineNumber":63,"sourceCode":"\n\tnonce := make([]byte, aead.NonceSize(), aead.NonceSize()+len(plaintext)+aead.Overhead())\n\t_, err = cryptorand.Read(nonce)\n\tif err != nil {\n\t\treturn \"\", errors.WithStack(err)\n\t}\n\n\tciphertext := aead.Seal(nonce, nonce, plaintext, additionalData)\n\treturn base64.URLEncoding.EncodeToString(ciphertext), nil\n}\n\nfunc (x *XChaCha20Poly1305) Decrypt(ctx context.Context, ciphertext string, aad []byte) (plaintext []byte, err error) {\n\tmsg, err := base64.URLEncoding.DecodeString(ciphertext)\n\tif err != nil {\n\t\treturn nil, errors.WithStack(err)\n\t}\n\n\tif len(msg) < chacha20poly1305.NonceSizeX {\n\t\treturn nil, errors.WithStack(fmt.Errorf(\"malformed ciphertext: too short\"))\n\t}\n\tnonce, ciphered := msg[:chacha20poly1305.NonceSizeX], msg[chacha20poly1305.NonceSizeX:]\n\n\tkeys, err := allKeys(ctx, x.d)\n\tif err != nil {\n\t\treturn nil, errors.WithStack(err)\n\t}\n\n\tvar aead cipher.AEAD\n\tfor _, key := range keys {\n\t\taead, err = chacha20poly1305.NewX(key)\n\t\tif err != nil {\n\t\t\tcontinue\n\t\t}\n\t\tplaintext, err = aead.Open(nil, nonce, ciphered, aad)\n\t\tif err == nil {\n\t\t\treturn plaintext, nil\n\t\t}","sourceCodeStart":45,"sourceCodeEnd":81,"githubUrl":"https://github.com/ory/hydra/blob/4174065ffb052799890f7480f5360a877a67ffc1/aead/xchacha20.go#L45-L81","documentation":"Decrypt base64-url-decodes the ciphertext and requires at least chacha20poly1305.NonceSizeX bytes so a nonce can be split off. A decoded message shorter than the nonce size cannot possibly be valid XChaCha20-Poly1305 output, so Decrypt returns 'malformed ciphertext: too short'.","triggerScenarios":"Calling Decrypt with a string that is not output of Encrypt: truncated database value, empty string, hand-crafted or corrupted token, wrong encoding (raw/hex instead of base64url) yielding a too-short decode.","commonSituations":"Data written by an older/other algorithm in the same column; manual DB edits or migrations truncating TEXT columns; passing a plain secret string instead of an encrypted token; different keys causing misinterpretation (though that usually fails auth, not length).","solutions":["Ensure the value passed to Decrypt is the exact string returned by Encrypt (base64url, untouched).","Check the storage column is large enough and the value was not truncated (log length before decrypt).","Re-encrypt the data: decrypt with the original mechanism/key, then Encrypt again with the current key set.","Guard call sites: only attempt Decrypt on values produced by Encrypt, return a fallback for legacy formats."],"exampleFix":"// before\ntoken := strings.Split(raw, \":\")[1] // custom mangling\nval, err := x.Decrypt(ctx, token)\n\n// after\nval, err := x.Decrypt(ctx, raw) // pass stored ciphertext as-is\nif err != nil { return nil, fmt.Errorf(\"unreadable token: %w\", err) }","handlingStrategy":"validation","validationCode":"// Cheap pre-check before calling Decrypt:\nif raw == \"\" || base64.RawURLEncoding.DecodedLen(len(raw)) < 24 { // XChaCha nonce size\n    return fmt.Errorf(\"not a valid encrypted token\")\n}","typeGuard":"func looksLikeCiphertext(s string) bool {\n    if s == \"\" { return false }\n    msg, err := base64.URLEncoding.DecodeString(s)\n    return err == nil && len(msg) >= chacha20poly1305.NonceSizeX\n}","tryCatchPattern":"val, err := x.Decrypt(ctx, ct)\nif err != nil {\n    if strings.Contains(err.Error(), \"malformed ciphertext\") {\n        return nil, ErrCorruptToken // treat as absent/invalid, do not retry\n    }\n    return nil, err\n}","preventionTips":["Only pass values returned by Encrypt to Decrypt.","Use sufficiently wide DB columns and verify no truncation on write.","Keep old keys in rotated_keys so data encrypted before rotation stays readable.","Add a migration path/re-encryption step when changing storage format or algorithm."],"tags":["encryption","aead","decryption","data-corruption"],"backgroundTag":"malformed-ciphertext","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"}