{"record":{"id":"fe55d307df86e704","repo":"FiloSottile/age","slug":"invalid-ciphertext-size","errorCode":null,"errorMessage":"invalid ciphertext size","messagePattern":"invalid ciphertext size","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"tag/tag.go","lineNumber":116,"sourceCode":"}\n\nfunc (r *Recipient) Wrap(fileKey []byte) ([]*age.Stanza, error) {\n\ts, _, err := r.WrapWithLabels(fileKey)\n\treturn s, err\n}\n\n// Tag computes the 4-byte tag for the given ciphertext enc.\n//\n// This is a low-level method exposed for use by plugins that implement\n// identities compatible with tagged recipients.\nfunc (r *Recipient) Tag(enc []byte) ([]byte, error) {\n\tlabel, tagRecipient := \"age-encryption.org/p256tag\", r.Bytes()\n\tif r.Hybrid() {\n\t\tlabel = \"age-encryption.org/mlkem768p256tag\"\n\t\t// In hybrid mode, the tag is computed over just the P-256 part.\n\t\ttagRecipient = tagRecipient[mlkem.EncapsulationKeySize768:]\n\t\tif len(enc) != mlkem.CiphertextSize768+uncompressedPointSize {\n\t\t\treturn nil, fmt.Errorf(\"invalid ciphertext size\")\n\t\t}\n\t} else if len(enc) != uncompressedPointSize {\n\t\treturn nil, fmt.Errorf(\"invalid ciphertext size\")\n\t}\n\trh := sha256.Sum256(tagRecipient)\n\ttag, err := hkdf.Extract(sha256.New, append(slices.Clip(enc), rh[:4]...), []byte(label))\n\tif err != nil {\n\t\treturn nil, fmt.Errorf(\"failed to compute tag: %v\", err)\n\t}\n\treturn tag[:4], nil\n}\n\n// WrapWithLabels implements [age.RecipientWithLabels], returning a single\n// \"postquantum\" label if r is a hybrid P-256 + ML-KEM-768 recipient. This\n// ensures a hybrid Recipient can't be mixed with other recipients that would\n// defeat its post-quantum security.\n//\n// To unsafely bypass this restriction, wrap Recipient in an [age.Recipient]","sourceCodeStart":98,"sourceCodeEnd":134,"githubUrl":"https://github.com/FiloSottile/age/blob/b74dce4cdbe35b5e5f66c06d9612b72f89028758/tag/tag.go#L98-L134","documentation":"Tag computes the 4-byte correlation tag over an HPKE ciphertext (enc). For a hybrid recipient the enc must be exactly mlkem.CiphertextSize768 (1088) + uncompressedPointSize (65) = 1153 bytes. Any other length is rejected because the HKDF input would be structurally wrong.","triggerScenarios":"Calling Recipient.Tag(enc) with an enc slice whose length differs from 1153 bytes while the recipient is hybrid — e.g. passing only the P-256 part, a truncated/stanza-mangled enc, or an enc produced for a classic recipient.","commonSituations":"Plugin authors implementing compatible identities re-deriving the tag from a stanza's second argument and getting a decoded value of the wrong length due to base64 corruption or manual slicing; mixing classic and hybrid stanzas.","solutions":["Ensure enc is the full raw HPKE encapsulation key: 1088-byte ML-KEM-768 ciphertext plus 65-byte uncompressed P-256 point (1153 bytes total).","Pass the exact second stanza argument after base64-raw decoding, without truncation or re-slicing.","Confirm the recipient is actually hybrid before applying hybrid length expectations (use r.Hybrid()).","Regenerate the stanza via WrapWithLabels if the enc value came from external storage and may be corrupted."],"exampleFix":"// before\nif len(enc) != 65 { return errors.New(\"bad enc\") }\ntag, err := r.Tag(enc)\n// after\nwant := 65\nif r.Hybrid() { want = 1088 + 65 }\nif len(enc) != want { return fmt.Errorf(\"bad enc size %d, want %d\", len(enc), want) }\ntag, err := r.Tag(enc)","handlingStrategy":"validation","validationCode":"const hybridEncSize = 1088 + 65 // mlkem.CiphertextSize768 + uncompressedPointSize\nfunc validHybridEnc(enc []byte) bool { return len(enc) == hybridEncSize }","typeGuard":"func isHybridEnc(enc []byte, r *tag.Recipient) bool { return r.Hybrid() && len(enc) == 1088+65 }","tryCatchPattern":"tagBytes, err := r.Tag(enc)\nif err != nil {\n    return fmt.Errorf(\"tag computation failed (hybrid enc len=%d): %w\", len(enc), err)\n}","preventionTips":["Hybrid enc must be exactly 1153 bytes (1088 + 65).","Decode the full stanza base64 argument; never slice it.","Check r.Hybrid() to pick the expected length."],"tags":["go","age","hpke","ciphertext","size-validation","post-quantum"],"backgroundTag":"invalid-ciphertext-size","analyzedSha":"b74dce4cdbe35b5e5f66c06d9612b72f89028758","analyzedAt":"2026-08-31T23:59:31.627Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T05:18:18.240Z"}