{"record":{"id":"581868fa638007be","repo":"golang/go","slug":"export-only-instantiation","errorCode":null,"errorMessage":"export-only instantiation","messagePattern":"export-only instantiation","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/crypto/hpke/hpke.go","lineNumber":173,"sourceCode":"\tsharedSecret, err := k.decap(enc)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tcontext, err := newContext(sharedSecret, k.KEM().ID(), kdf, aead, info)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\treturn &Recipient{context}, nil\n}\n\n// Seal encrypts the provided plaintext, optionally binding to the additional\n// public data aad.\n//\n// Seal uses incrementing counters for each call, and Open on the receiving side\n// must be called in the same order as Seal.\nfunc (s *Sender) Seal(aad, plaintext []byte) ([]byte, error) {\n\tif s.aead == nil {\n\t\treturn nil, errors.New(\"export-only instantiation\")\n\t}\n\tciphertext := s.aead.Seal(nil, s.nextNonce(), plaintext, aad)\n\ts.seqNum++\n\treturn ciphertext, nil\n}\n\n// Seal instantiates a single-use HPKE sending HPKE context like [NewSender],\n// and then encrypts the provided plaintext like [Sender.Seal] (with no aad).\n// Seal returns the concatenation of the encapsulated key and the ciphertext.\nfunc Seal(pk PublicKey, kdf KDF, aead AEAD, info, plaintext []byte) ([]byte, error) {\n\tenc, s, err := NewSender(pk, kdf, aead, info)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tct, err := s.Seal(nil, plaintext)\n\tif err != nil {\n\t\treturn nil, err\n\t}","sourceCodeStart":155,"sourceCodeEnd":191,"githubUrl":"https://github.com/golang/go/blob/b6b368adc57c96c3151d224d172029f233ead2c3/src/crypto/hpke/hpke.go#L155-L191","documentation":"Returned by Sender.Seal when the HPKE context was instantiated with the export-only AEAD (AEAD ID 0xFFFF per RFC 9180 §7.3). An export-only context computes the shared secret and exporter secret but intentionally leaves context.aead nil, so it can derive secrets via Export() but cannot perform authenticated encryption. Seal/Open explicitly guard against this by checking s.aead == nil.","triggerScenarios":"Calling (*Sender).Seal(aad, plaintext) on a Sender produced by NewSender with the export-only AEAD (e.g. hpke.ExportOnly() or a custom AEAD whose aead() returns a nil cipher.AEAD). Also reachable if a user constructs a context-like value whose aead field is nil.","commonSituations":"Developers copy the ciphersuite triple (KEM, KDF, AEAD) from a spec table and pick the 0xFFFF export-only AEAD ID by mistake. Or they intend to use Export() for key derivation but call Seal() expecting encryption to work. Sometimes seen when porting RFC 9180 test vectors that exercise the export-only mode.","solutions":["Pass a real AEAD to NewSender, e.g. AES-128-GCM (hpke.AES128GCM) or ChaCha20Poly1305, instead of the export-only AEAD.","If you only need derived secrets, call Sender.Export(exporterContext, length) rather than Seal().","Verify the AEAD before constructing the Sender: check aead.ID() != 0xFFFF before calling NewSender."],"exampleFix":"// before\naead := hpke.ExportOnly()\nenc, s, err := hpke.NewSender(pk, kdf, aead, info)\nct, err := s.Seal(nil, plaintext) // returns \"export-only instantiation\"\n\n// after\naead := hpke.AES128GCM()\nenc, s, err := hpke.NewSender(pk, kdf, aead, info)\nct, err := s.Seal(nil, plaintext)","handlingStrategy":"validation","validationCode":"// Reject export-only AEAD before constructing the sender.\nfunc newEncryptingSender(pk hpke.PublicKey, kdf hpke.KDF, aead hpke.AEAD, info []byte) (enc []byte, s *hpke.Sender, err error) {\n    if aead == nil || aead.ID() == 0xFFFF {\n        return nil, nil, errors.New(\"aead is export-only; cannot Seal\")\n    }\n    return hpke.NewSender(pk, kdf, aead, info)\n}","typeGuard":null,"tryCatchPattern":"// Treat as a programmer error, not a recoverable runtime condition.\nct, err := s.Seal(aad, plaintext)\nif err != nil {\n    if err.Error() == \"export-only instantiation\" {\n        log.Fatal(\"sender built with export-only AEAD; pass AES128GCM/ChaCha20Poly1305\")\n    }\n    return err\n}","preventionTips":["Centralize ciphersuite selection in one helper that never returns the export-only AEAD for encryption contexts.","Add a unit test that asserts Seal succeeds on every (KEM,KDF,AEAD) tuple your app uses.","Treat AEAD ID 0xFFFF as a derivation-only mode in your mental model and docs."],"tags":["hpke","cryptography","aead","go"],"analyzedSha":"b6b368adc57c96c3151d224d172029f233ead2c3","analyzedAt":"2026-08-12T00:22:02.250Z","schemaVersion":2},"datasetVersion":"2026-08-12T06:17:24.410Z"}