54 min read

The Standard Was Boring; That Was the Point: A Field Guide to HPKE (RFC 9180)

The industry standardized encrypt-to-a-public-key four incompatible ways. HPKE (RFC 9180) is the boring, correct fix -- and its non-goals are the whole discipline.

Permalink

1. An Elementary Task the Industry Got Wrong Four Times

"Encrypt this to their public key" is the first promise public-key cryptography ever made. It is the one-line pitch of RSA, the thing every engineer assumes was solved and made boring decades ago, a settled corner of the field you reach for without thinking. Yet for roughly twenty-five years there was no single correct way to do it.

At least four "standard" versions of the Elliptic Curve Integrated Encryption Scheme (ECIES) shipped: ANSI X9.63 in 2001, IEEE 1363a in 2004, ISO/IEC 18033-2 in 2006, and SECG's SEC 1 version 2 in 2009 [2], [3]. They disagreed on the key-derivation function, the message authentication code, whether elliptic-curve points were compressed or uncompressed, and which optional fields went into the hash. Two conforming implementations, handed the same curve and message, could produce ciphertexts that would not decrypt for each other.

None of the four came with shared test vectors, so two libraries had no ground truth against which to check that they agreed. And one standard openly documented that an attacker could take a valid ciphertext and manufacture a second, different valid ciphertext for the same plaintext. It named the phenomenon "benign malleability" and deemed it harmless [1].

The hard part of "encrypt to a public key," it turned out, was never the cryptography. It was getting everyone to do the same correct thing, with a proof that it was correct and a way to check that two implementations interoperated. That failure was tolerable when public-key encryption was a batch operation between parties who could negotiate a format. It stopped being tolerable the moment the modern web needed to encrypt to a server it had never spoken to.

Consider what TLS Encrypted Client Hello and Oblivious HTTP actually require. A client must encrypt the sensitive part of its connection -- in Encrypted Client Hello, the server name it is trying to reach -- to a public key it fetched moments earlier, for a server it has exchanged no messages with, with no round trip in which to negotiate anything [7], [9]. There is no handshake to agree on a KDF or a point encoding. The format has to be right the first time, on the first byte, or the connection reveals exactly what it was trying to hide.

That is the setting Hybrid Public Key Encryption was built for. HPKE, specified in RFC 9180 by the IRTF's Crypto Forum Research Group in February 2022, is the boring, correct answer [3]. Its headline achievements sound almost too unglamorous to matter: it publishes test vectors, and it comes with a proof that it is secure against chosen-ciphertext attack.

Those two boring facts are exactly why HPKE matters. Once you accept them, the interesting question is not whether HPKE is secure but a sharper one, worth carrying through every section that follows: you have a stranger's public key and one shot to send them a secret, with no handshake and no reply -- what does HPKE buy you, and what does it silently refuse to give?

This is Part 19 of a field guide for people who design protocols, and it is the part where the primitives from earlier parts -- authenticated encryption, key derivation, elliptic curves, the KEM-DEM idea, digital signatures -- stop being separate ingredients and compose into a single shipped building block. By the end you will be able to look at any "encrypt to a recipient's public key" design and name, on sight, which of HPKE's modes it is using and which of HPKE's non-goals it is quietly assuming away.

The fix is so unglamorous it is tempting to skip straight to the API. Do not: the shape of HPKE is written in the failures of the four standards it replaced, and you cannot use it safely without seeing what "done right" was correcting. Start with the one idea all four were circling and none quite pinned down: the difference between encrypting a key and encapsulating one.

2. Encapsulate the Key, Do Not Encrypt It

RFC 9180 describes what it does differently in a single line, and that line is the whole conceptual pivot of this article. The traditional combination, it says, has been to "encrypt the symmetric key with the public key." HPKE instead will "generate the symmetric key and its encapsulation with the public key" [3]. That one-word swap -- from encrypt to encapsulate -- is the difference between the padding-oracle-prone lineage that broke RSA key transport and a clean, composable, provable construction.

The traditional combination has been "encrypt the symmetric key with the public key." HPKE instead will "generate the symmetric key and its encapsulation with the public key." -- RFC 9180

Build the mental model in three moves. Derive only what the rest of the article needs.

Move one: KEM-DEM in one picture

Hybrid Public Key Encryption (HPKE)

The scheme standardized in RFC 9180 for encrypting arbitrary-length messages to a recipient's public key. It is built from three interchangeable ingredients -- a Key Encapsulation Mechanism (KEM), a Key Derivation Function (KDF), and an Authenticated Encryption with Associated Data (AEAD) scheme -- named together as a ciphersuite.

The word "hybrid" means it splits the work between public-key and symmetric cryptography, and the split follows the KEM-DEM pattern. The KEM does the public-key half; the DEM does the symmetric half.

Key Encapsulation Mechanism (KEM)

A public-key primitive that does not transport a key you chose. Called with the recipient's public key, Encap(pkR) generates a fresh random shared secret together with an encapsulation enc of it, returning the pair (shared_secret, enc). The recipient runs Decap(skR, enc) to recover the same shared secret. You never pick the key; the public-key operation produces it.

Data Encapsulation Mechanism (DEM)

The symmetric half of a hybrid scheme: an AEAD that bulk-encrypts the actual payload under a key derived from the KEM's shared secret. In HPKE the DEM is a single AEAD scheme, and its authenticity tag is intrinsic rather than bolted on.

If you have read Part 14 on getting RSA right, this is the same KEM-DEM skeleton, now instantiated on elliptic curves rather than RSA. The reason to generate the key rather than encrypt one is the entire lesson of that part: the moment you encrypt an attacker-influenced key under a public-key operation, you invite a decryption oracle to leak information one query at a time. A KEM removes the attacker's chosen input from the public-key step. There is nothing to pad, and nothing to probe.

Ctrl + scroll to zoom
The one-word swap the whole article turns on: encrypting a chosen key (left) versus letting the public-key operation generate the key (right)

Move two: DHKEM, concretely

HPKE's deployed KEM is Diffie-Hellman-based, and it is worth seeing the mechanism once rather than treating it as a black box.

DHKEM

HPKE's Diffie-Hellman-based KEM. Encap(pkR) generates an ephemeral key pair, computes a Diffie-Hellman value against the recipient's public key, and derives the shared secret from it; the encapsulation enc is the serialized ephemeral public key. The authenticated variants AuthEncap/AuthDecap additionally fold in a Diffie-Hellman value computed with the sender's static key.

Concretely, Encap(pkR) generates an ephemeral pair (skE,pkE)(sk_E, pk_E), computes DH(skE,pkR)\mathrm{DH}(sk_E, pk_R), sets enc to the serialization of pkEpk_E, and returns shared_secret = ExtractAndExpand(dh, kem_context), where kem_context already contains both enc and the recipient key pkRpk_R [3]. The recipient recomputes the identical Diffie-Hellman value as DH(skR,pkE)\mathrm{DH}(sk_R, pk_E) and derives the same secret. This is exactly the ephemeral-static shape that DHIES introduced in the 1990s, now wearing a modern KDF -- and the decision to fold pkRpk_R into kem_context is the first appearance of the idea that later kills benign malleability.

Ctrl + scroll to zoom
DHKEM Encap and Decap: the sender's ephemeral key produces both the shared secret and its encapsulation, and the recipient recomputes the same Diffie-Hellman value

Move three: the key schedule and the Context

The shared secret is not used directly. Both sides run it through a key schedule.

Key schedule (HPKE)

The HKDF Extract-then-Expand process that turns the KEM's shared_secret into working keys. It extracts a pseudorandom key from the shared secret (and, in PSK modes, the PSK), then expands that key over a context of the mode identifier, a hash of the pre-shared-key identifier (psk_id_hash), and a hash of the application info string (info_hash) to output the AEAD key, a base_nonce, and an exporter_secret.

AEAD (Authenticated Encryption with Associated Data)

A symmetric primitive that provides confidentiality and integrity at once: it encrypts a plaintext and authenticates both the ciphertext and some associated data, so any tampering is detected on decryption. In HPKE it is the DEM that seals the payload; AES-128-GCM, AES-256-GCM, and ChaCha20-Poly1305 are the standard choices.

The HPKE key schedule is, quite literally, a worked example of the HKDF construction from Part 13: extract a pseudorandom key from the shared secret, then expand it into the specific bytes each downstream primitive needs. Its outputs assemble into a Context. The Context exposes Seal/Open -- a stateful pair in which an incrementing sequence number is combined with base_nonce (by exclusive-or) to give every message a distinct nonce -- and Export.

Export (secret export)

An HPKE interface that derives fresh, independent secrets of any length from the established context, for keying other protocols. It behaves as a variable-length pseudorandom function, so exported secrets are independent of the AEAD key and of each other. Oblivious HTTP and Oblivious DNS use it to key their own message framing instead of calling Seal directly.

For a single message you do not need to hold a Context open at all: the single-shot Seal/Open API wraps setup, one encryption, and teardown into one call. Everything above -- encapsulate, derive, seal -- is what those single-shot calls do underneath.

Ctrl + scroll to zoom
The HPKE construction end to end, showing where the public keys are bound into the derivation

The structural model below is a toy, not real cryptography, but it lets you feel the difference from wrapping a chosen key: encap invents a random secret and its encapsulation, folds in the recipient's public key, and shows that a different recipient key yields a different secret from the same encapsulation.

JavaScript Structural model: Encap generates the key, and the recipient key is bound into it (NOT real crypto)
// STRUCTURAL MODEL, NOT REAL CRYPTO. It only shows the SHAPE of "generate the key".
function toyMix(str) {                       // a tiny non-cryptographic mixer
let h = 2166136261 >>> 0;
for (const ch of str) { h ^= ch.charCodeAt(0); h = Math.imul(h, 16777619) >>> 0; }
return (h >>> 0).toString(16);
}
function randInt() { return Math.floor(Math.random() * 1e9); }

// Encap: the PUBLIC-KEY op GENERATES the shared secret. The caller never picks it.
function encap(pkR) {
const skE = randInt();                     // fresh ephemeral secret
const enc = 'pkE(' + skE + ')';            // its public value IS the encapsulation
const dh  = toyMix(enc + '|' + pkR);       // stands in for DH(skE, pkR)
// kem_context binds BOTH enc and pkR into the derivation -- the anti-malleability trick
const shared_secret = toyMix('ss|' + dh + '|' + enc + '|' + pkR);
return { shared_secret, enc };
}

const pkR = 'recipient-key-A';
const { shared_secret, enc } = encap(pkR);
console.log('enc on the wire     = ' + enc);
console.log('shared_secret       = ' + shared_secret);

// Same enc, a DIFFERENT recipient key -> a DIFFERENT secret. A swapped or mangled
// public key simply cannot derive the key that decrypts, because pkR is bound in.
const swapped = toyMix('ss|' + toyMix(enc + '|recipient-key-B') + '|' + enc + '|recipient-key-B');
console.log('same enc, key B     = ' + swapped);
console.log('secrets differ      = ' + (swapped !== shared_secret));

Press Run to execute.

Now the payoff, and the first place your understanding should shift.

Encapsulate, do not encrypt. Let the public-key operation generate the symmetric key and its encapsulation together (a KEM), then AEAD-encrypt under a derived key (a DEM). Because the composite is secure against chosen-ciphertext attack whenever both halves are, "encrypt to a public key" stops being an ad-hoc scheme you must re-prove each time and becomes a composition of two well-understood parts.

The modularity is the whole point. Cramer and Shoup proved in 2001 that a KEM and a DEM, each secure against chosen-ciphertext attack, compose into a hybrid scheme that is too [10]; a few years later the converse was pinned down, showing both halves essentially have to be that strong for the composite to be [11]. Those results turn "is this encrypt-to-a-public-key scheme secure?" into two smaller, reusable questions. RFC 9180 notes in passing that its Base mode and the original Cramer-Shoup scheme are both "named HPKE" -- the name has KEM-DEM in its bones [3], [10].

This clean picture -- encapsulate, derive, seal -- looks obvious in hindsight. It was not. The KEM-DEM idea took two decades to take its final shape, and the Diffie-Hellman instantiation it rests on fragmented into the four broken standards from the last section before anyone consolidated it. To understand why HPKE makes the exact choices it does, walk the twenty-five years it is the endpoint of.

3. From "Encrypt the Key" to "Encapsulate the Key"

The story is a twenty-five-year march from encrypt the key to encapsulate the key, starting, as RFC 9180 notes, in the early days of public-key cryptography [3]. Diffie and Hellman defined the idea of a public key in 1976 but did not say how to encrypt a long message to one [12]. The answers accumulated in layers, each fixing the previous one's sharpest failure.

Ctrl + scroll to zoom
The twenty-five-year arc from encrypting a chosen key to encapsulating a generated one, ending in RFC 9180 and the post-quantum drop-in

PEM (RFC 1421, 1993): the traditional combination. Privacy Enhanced Mail defined the canonical early hybrid. Pick a content-encryption key, encrypt that key under the recipient's public key, and bulk-encrypt the message symmetrically. RFC 9180 opens by naming this exact tradition and positioning HPKE as the departure from it [13], [3]. It is the naive model most engineers still carry, and the one the previous section asked you to give up.

ElGamal (1985) and why Diffie-Hellman resists "encrypt a chosen key." ElGamal's public-key encryption predates the hybrid schemes and shows the friction directly [14]. To "encrypt" a chosen symmetric key under a Diffie-Hellman public key, you must first encode that key as a group element, then pay a roughly twofold ciphertext expansion -- and the result is malleable rather than secure against chosen-ciphertext attack. That malleability is a reasoned inference here, anchored to DHIES's own claim to offer "stronger security properties than ElGamal," not a verbatim quote [15].

The RSA branch of the same idea failed even more loudly. Encrypting a chosen key under RSA with PKCS#1 v1.5 padding is precisely the construction that Bleichenbacher's attack turned into a decryption oracle, revived two decades later as ROBOT against nearly a third of the top 100 web domains [16], [17], [18]. The part of this series on getting RSA right tells that story in full; both branches teach the same lesson: stop choosing a key and wrapping it.

DHIES and DHAES (Abdalla, Bellare, Rogaway, 1999): derive, do not encrypt. The academic root of ECIES made the decisive move. Instead of encrypting a key, run an ephemeral-static Diffie-Hellman exchange, push the result through a KDF to get an encryption key and a MAC key, then Encrypt-then-MAC the payload. No key is ever encrypted; it is agreed.

The authors proved the scheme secure against chosen-ciphertext attack under the Oracle Diffie-Hellman assumption, with "no random oracles involved," and called it "an attractive starting point for developing public-key encryption standards" [19], [15]. DHIES has no single crisp date: the DHAES manuscript appeared as ePrint 1999/007 in 1999, was later renamed DHIES, and its formal analysis was published at CT-RSA 2001. Cite the range, not one year [19], [15]. That parenthetical -- "an attractive starting point for developing standards" -- turned out to be prophetic in the worst way.

Cramer and Shoup (2001): the modular abstraction. DHIES hinted at a structure; Cramer and Shoup formalized it. A KEM transports a random key, a DEM encrypts the payload, and the composite is secure against chosen-ciphertext attack if both halves are [10]. A few years later the necessity direction was worked out: for the standard hybrid, both halves essentially have to reach that bar [11]. This is the theory HPKE would eventually standardize. It existed, complete, in 2001.

So by 2001 everything HPKE needed already existed: a concrete, provably secure ephemeral-static scheme, and an abstraction that made "encrypt to a public key" a matter of plugging two proven pieces together. What happened over the following eight years was not progress. It was fragmentation. The same good idea was handed to four different standards bodies, and it came out four incompatible ways.

4. The ECIES Zoo

Between 2001 and 2009 the DHIES construction was standardized four times -- ANSI X9.63, IEEE 1363a, ISO/IEC 18033-2, and SEC 1 v2 -- collectively called ECIES, and collectively a mess that RFC 9180 politely calls "numerous competing and non-interoperable standards" [3], [2]. This is the hook's promise, paid off in detail. Three defects run through the era, and HPKE was built to close all three.

Ctrl + scroll to zoom
The single DHIES idea, standardized four incompatible ways between 2001 and 2009, then consolidated by RFC 9180 in 2022

Fragmentation: the "which ECIES?" problem. The four standards disagree on the key-derivation function, the MAC, whether elliptic-curve points are compressed or uncompressed, and whether the optional SharedInfo fields are present. So two conforming implementations often cannot interoperate: an ECIES ciphertext built with the ANSI X9.63 KDF and one built to ISO/IEC 18033-2, for the same curve and message, are byte-incompatible [3], [2]. The concrete interoperability failure is not hypothetical: swap the KDF or the point-encoding convention between two of the four standards and a ciphertext valid under one is undecryptable under another, for identical inputs [2].

Benign malleability. SEC 1 version 2 documents a specific defect and then waves it off.

Benign malleability

A property of the ECIES standards in which an attacker can transform a valid ciphertext into a different valid ciphertext for the same plaintext -- concretely, by replacing the ephemeral point RR with R-R, which shares the same x-coordinate. SEC 1 called it "benign malleability" and "generally deemed harmless," and offered binding RR into SharedInfo only as an optional countermeasure. Because a ciphertext can be mauled into a second valid ciphertext, the scheme as written is not strictly secure against chosen-ciphertext attack [1].

The word "benign" is doing a great deal of work. A scheme that lets anyone produce a second valid ciphertext for a plaintext they cannot read is, by definition, malleable, and malleability is exactly what security against chosen-ciphertext attack forbids. SEC 1 made the fix -- include RR in the hashed context -- optional. Hold that thought: it is the single design decision the next section shows HPKE making mandatory.

No proofs, and no test vectors. RFC 9180 is blunt about the state it inherited: prior schemes "rely on outdated primitives, lack proofs of IND-CCA2 security, or fail to provide test vectors" [3]. The missing test vectors are not a documentation nicety. Without a published set of known-good inputs and outputs, two implementers have no way to discover that their libraries disagree until a ciphertext fails to decrypt in production -- which is precisely how "conformant" implementations drifted apart.

The comparison below is the historical spine as a grid. Read the last two columns -- proof status and test vectors -- and the shape of the fix becomes obvious.

ApproachCore ideaMain weaknessProof and test vectorsStatus
RSA key transport (PKCS#1 v1.5)Encrypt a chosen key under RSAPadding oracle (Bleichenbacher, ROBOT)No CCA2 proof; padding fragileDeprecated in TLS 1.3
ElGamal encryptionEncode a chosen key as a group elementMalleable; roughly 2x expansionNo CCA2 proofHistorical
DHIES / DHAESDerive the key from ephemeral-static DHUnderspecified as a deployable formatCCA2 proof (ODH); no shared vectorsAcademic root of ECIES
ECIES (four standards)Standardize DHIESFragmented; benign malleabilityProof gaps; no shared vectorsLegacy interop only
HPKE (RFC 9180)KEM-DEM, all public keys bound inNon-goals must be respectedIND-CCA2 proof; per-suite vectorsCurrent default
HPKE + X-WingSwap DHKEM for a hybrid PQ KEMLarger ciphertexts; draft-stageHybrid PQ proof; draft vectorsEmerging

Every one of these defects points at the same missing discipline: pin one construction, bind the ambiguous inputs so malleability becomes impossible, prove the result, and publish vectors so anyone can check interoperation. That list is not a wishlist. It is, almost line for line, the changelog of RFC 9180. Here is what "done right" actually changed.

5. "Done Right" Was Discipline, Not Genius

There is no eureka moment in this story, and that is the point. HPKE's virtue is not a clever new algorithm -- the algorithm, KEM-DEM, was twenty years old when RFC 9180 shipped. Its virtue is four consolidating decisions, made once, correctly, with the proof and the test vectors published in the same body of work. Each decision closes one of the previous section's defects.

Decision one: abstract the KEM so the primitive is agile. Instead of freezing one curve, one KDF, and one MAC into a single scheme, HPKE is parameterized over a ciphersuite of three identifiers: kem_id, kdf_id, and aead_id [3]. This looks like a small thing and is not. Fragmentation was the disease of the ECIES era, where each standards body froze a different combination.

HPKE replaces uncontrolled fragmentation with managed agility: many ciphersuites, but one construction and one set of rules governing all of them. When a component needs replacing -- as the post-quantum transition will require -- you change an identifier, not the scheme.

Decision two: bind every public key into the key schedule. This is the decisive fix, and RFC 9180 states it in one sentence.

"HPKE mitigates malleability problems (called benign malleability) in prior public key encryption standards based on ECIES by including all public keys in the context of the key schedule." -- RFC 9180, Section 9.1

What SEC 1 made optional, HPKE makes mandatory [3], [1]. Recall the benign-malleability trick: replace the ephemeral point RR with R-R to get a second valid ciphertext. In HPKE the public keys are inputs to the derived keys, so a mauled encapsulation derives a different key schedule and simply fails to produce a valid decryption. The attack does not become hard; it becomes impossible to express. The malleability is not patched -- it is designed out.

Decision three: mandate a single AEAD as the DEM. Gone is the ad-hoc two-key Encrypt-then-MAC with per-standard knobs for the cipher, the MAC, and the tag order. HPKE specifies one AEAD scheme per ciphersuite, so authenticity is intrinsic to the primitive rather than assembled by the caller [3]. If the series' part on safe authenticated-encryption composition has a single thesis -- compose it wrong and the tag verifies while the forgery goes through -- then HPKE is simply that "compose it correctly" rule frozen into a specification.

Decision four: ship proofs and test vectors, developed alongside the standard. This is the part the ECIES era never had. HPKE began as an individual draft in 2019 and was analyzed as it was written [20]. Lipp's CryptoVerif analysis mechanically checked all four modes; the analysis by Alwen, Blanchet, Hauck, Kiltz, Lipp, and Riepel gave exact security bounds for the authenticated mode and was published at EUROCRYPT 2021; and the two pre-shared-key modes were later closed under the Gap Diffie-Hellman assumption [4], [5], [21]. Every ciphersuite ships machine-checkable test vectors in the RFC itself [3].

IND-CCA2 (indistinguishability under adaptive chosen-ciphertext attack)

The standard security goal for public-key encryption. An adversary who may submit ciphertexts of its choice to a decryption oracle -- adaptively, even after seeing the challenge -- still cannot tell which of two chosen plaintexts a challenge ciphertext encrypts. It is the bar the ECIES standards could not prove and that malleability violates, and the bar HPKE's composition meets when its KEM and AEAD do [10], [11].

Put the four together and your sense of what happened should shift.

"Done right" was consolidation, not invention. Abstract the KEM for agility, bind every public key into the key schedule so benign malleability cannot happen, mandate one AEAD instead of ad-hoc Encrypt-then-MAC, and ship machine-checked proofs and per-ciphersuite test vectors in the same document. The innovation is rigor -- which is exactly why HPKE is boring, and why boring is the achievement.

There is even a small tell that HPKE was designed to be built on, not just used directly. HPKE reserves an export-only AEAD identifier (the value 0xFFFF) for ciphersuites that never call Seal and only ever use the Export interface -- a strong hint that the authors expected HPKE to be keyed into other protocols rather than used standalone [3].

Four decisions turn a fragmented, unprovable family into one boring, correct primitive. But "one primitive" is not "one behavior." HPKE ships a small menu -- four modes -- and choosing the wrong one is the first and most common way to misuse it. The menu is where the design stops being about the math and starts being about what you are trying to authenticate.

6. The Four Modes: HPKE's Core Menu

RFC 9180, Section 5.1, defines exactly four modes, and the difference between them reduces to a single question: what extra secret or identity gets folded into the key schedule? Get the answer wrong and you either fail to authenticate a sender you needed to, or -- worse -- come to believe you authenticated one you did not [3]. There are four modes, never three: the pre-shared-key-plus-authenticated combination is a real, distinct mode that is easy to forget.

ModeIDExtra inputWhat it authenticatesUse when
mode_base0x00noneOnly that the ciphertext is for this recipient keyYou have just the recipient's public key
mode_psk0x01a pre-shared keyRecipient, plus possession of the PSKA symmetric secret already exists out of band
mode_auth0x02sender's static keyThat a specific sender key holder sent itThe recipient must know which sender key sealed it
mode_auth_psk0x03static key and PSKBoth a sender identity and PSK possessionYou need sender identity and a shared secret

mode_base (0x00): nothing extra. The sender is anonymous; the derivation authenticates only that the ciphertext was sealed to this recipient's key. It is the default -- and, tellingly, the mode that every flagship deployment in the next section uses.

mode_psk (0x01): a pre-shared key. A symmetric secret known to both parties is folded into the schedule, so a valid decryption additionally proves the sender possessed the PSK.

mode_auth (0x02): the sender's static key. Here Encap/Decap are replaced by AuthEncap/AuthDecap, which fold a Diffie-Hellman value between the sender's static key and the recipient into the shared secret. A valid decryption now proves the message came from the holder of that specific sender key [3].

mode_auth_psk (0x03): both. A static sender identity and a shared secret, folded together.

Ctrl + scroll to zoom
The four modes are the same machinery with different inputs folded into the key schedule

Now the subtlety that trips up careful engineers. The authenticated modes are a form of signcryption, and they carry a security distinction imported from that theory.

Signcryption

A primitive that combines encryption and sender authentication in one operation, cheaper than encrypt-then-sign. Its authentication is defined relative to insiders and outsiders: an outsider is any third party, while an insider is a party who holds one of the long-term keys. HPKE's authenticated modes are signcryption, so their guarantees are stated in these terms rather than as a public, third-party-verifiable signature.

The consequence matters: mode_auth is not a digital signature. Because the recipient shares the Diffie-Hellman secret that authenticated the sender, the recipient could have produced the same ciphertext, so it cannot prove to any third party who the author was -- there is no non-repudiation. The analysis by Alwen, Blanchet, Hauck, Kiltz, Lipp, and Riepel makes this precise with an insider-versus-outsider treatment lifted from signcryption theory [5], [22]. If you want a third party to verify authorship, you want the digital-signature part of this series, not HPKE's auth mode.

The security properties proven for each mode, from RFC 9180's Table 6 via Lipp's mechanized analysis, are worth seeing side by side [3], [4]:

PropertyBasePSKAuthAuthPSK
Message confidentiality (IND-CCA2)YesYesYesYes
Export secrecy (variable-length PRF)YesYesYesYes
Sender authenticationN/AYesYesYes
Extra secret assumedrecipient key secretplus PSK secretplus sender key secretplus sender key and PSK

The structural model below makes the "same machinery, different inputs" claim concrete: it builds a stand-in key-schedule context for each mode and shows the derived key differs, though nothing about the construction changed.

JavaScript Structural model: the four modes differ only in what is folded into the key schedule (NOT real crypto)
// STRUCTURAL MODEL, NOT REAL CRYPTO. Shows only that mode changes the derived key.
function toyMix(s){let h=2166136261>>>0;for(const ch of s){h^=ch.charCodeAt(0);h=Math.imul(h,16777619)>>>0;}return (h>>>0).toString(16);}
const shared_secret = 'ss-from-KEM';

// Real HPKE: Extract a PRK from shared_secret, then Expand over mode + psk_id_hash + info_hash.
// This toy just mixes them (plus the sender key for auth modes) to show the mode changes the key.
function keySchedule(mode, pskId, senderKey) {
const psk_id_hash = toyMix('pskid:' + (pskId || ''));
const info_hash   = toyMix('info:my-app-v1');
const ss = senderKey ? toyMix(shared_secret + '|auth:' + senderKey) : shared_secret;
return toyMix(mode + '|' + psk_id_hash + '|' + info_hash + '|' + ss);
}

console.log('base     -> ' + keySchedule('0x00', null, null));
console.log('psk      -> ' + keySchedule('0x01', 'shared-psk', null));
console.log('auth     -> ' + keySchedule('0x02', null, 'sender-static-key'));
console.log('auth_psk -> ' + keySchedule('0x03', 'shared-psk', 'sender-static-key'));
console.log('all four derive different keys from identical machinery');

Press Run to execute.

Choosing a mode answers "who am I authenticating?" It says nothing about which curve, which KDF, which AEAD, and how not to shoot yourself with the nonce. That is the ciphersuite-and-usage layer -- where the test vectors finally earn their keep, and where the known HPKE implementation advisories actually live.

7. Ciphersuites and Correct Usage

A mode plus a ciphersuite is a complete HPKE configuration. The ciphersuite is three identifiers -- a KEM, a KDF, and an AEAD -- and the correct-usage rules are mostly about a single thing: never let the same nonce protect two messages.

SlotOptionsHow to choose
KEMDHKEM(X25519, HKDF-SHA256); DHKEM(P-256/P-384/P-521, HKDF-SHA256/384/512); DHKEM(X448, HKDF-SHA512)X25519 unless policy requires NIST curves; encapsulation enc is 32 bytes for X25519, 65 for P-256
KDFHKDF-SHA256, HKDF-SHA384, HKDF-SHA512Match the KDF hash to the KEM and AEAD strength
AEADAES-128-GCM, AES-256-GCM, ChaCha20-Poly1305, Export-only (0xFFFF)AES-GCM with AES hardware; ChaCha20-Poly1305 without; export-only when you never call Seal

The three menus map onto earlier parts of this series. DHKEM's curves are the elliptic curves of the curves part; pick X25519 for new designs, NIST curves only when a compliance regime demands them. The KDF slot is the HKDF of the key-derivation part, used exactly as designed. The AEAD slot is the decision from the AEAD part: AES-GCM where the CPU has AES instructions, ChaCha20-Poly1305 where it does not. The tag is 16 bytes in every case.

The nonce and sequence discipline is the crux. Inside a Context, the per-message nonce is computed as nonce = base_nonce XOR seq, where seq is an incrementing sequence number. The Context is therefore stateful, and the sequence number must never repeat and must never overflow, because AEAD nonce reuse is catastrophic: for AES-GCM it can leak the authentication key, and for any of these AEADs it breaks confidentiality outright [3]. This is the same hazard the nonce-reuse part of this series covers for AES-GCM, arriving here as a counter you must not let wrap.

The structural model below computes a few per-message nonces and then deliberately overflows a narrow counter so you can watch a nonce repeat -- which is, in miniature, exactly the implementation bug the misuse section names.

JavaScript Structural model: per-message nonce is base_nonce XOR seq, and overflow repeats a nonce (NOT real crypto)
// STRUCTURAL MODEL, NOT REAL CRYPTO. Toy 8-bit nonce; real HPKE nonces are 96 bits.
const base_nonce = 0xA7;
function nonceFor(seq) { return (base_nonce ^ (seq & 255)).toString(16); }

for (let seq = 0; seq !== 5; seq++) {
console.log('seq ' + seq + ' -> nonce ' + nonceFor(seq));
}

// A deliberately NARROW counter overflows after 256 messages and REPEATS a nonce.
const width = 256;
const seqA = 3;
const seqWrap = seqA + width;                 // a later message with the same low 8 bits
console.log('seq ' + seqA + '   -> nonce ' + nonceFor(seqA));
console.log('seq ' + seqWrap + ' -> nonce ' + nonceFor(seqWrap) + '   (wrapped)');
console.log('nonce reused after overflow = ' + (nonceFor(seqA) === nonceFor(seqWrap)));

Press Run to execute.

Two operational notes round out the usage rules. First, the AEAD message-count and plaintext-length limits are per-ciphersuite constants, and single-shot Seal/Open sidesteps the streaming bookkeeping entirely. When you need to protect many messages or key a different protocol's framing, use the Export interface to derive an independent secret rather than looping Seal on one long-lived Context -- this is exactly what Oblivious HTTP and Oblivious DNS do [3]. Second, the one default to remember: mode_base with DHKEM(X25519, HKDF-SHA256), HKDF-SHA256, and AES-128-GCM is the interop-safe choice, with ChaCha20-Poly1305 substituted on hardware without AES acceleration.

These rules read like an interop checklist because that is precisely what they are, and the only reason they can be a checklist is the test vectors: you can prove your library agrees with the specification byte for byte before you ship. But a checklist for using HPKE says nothing about where it has already won. The strongest evidence that HPKE is the right default is that the modern privacy stack is built on it.

8. Deployments as the Proof

The best argument that HPKE is state of the art in 2026 is not a benchmark. It is a deployment list. Every "encrypt to a party you have never spoken to" corner of the modern IETF stack runs on HPKE, and they almost all run on the same mode.

DeploymentSpecificationWhat HPKE doesMode and API
Encrypted Client HelloRFC 9849 (2026)Encrypts the inner ClientHello, including the server namemode_base, SetupBaseS/SetupBaseR
Messaging Layer SecurityRFC 9420 (2023)Encrypts path secrets to tree nodes in TreeKEMmode_base, SealBase
Oblivious HTTPRFC 9458 (2024)Encapsulates requests and responses to a gatewaymode_base plus Export
Oblivious DNS over HTTPSRFC 9230 (2022)Encrypts DNS queries to a target through a proxymode_base plus Export

Encrypted Client Hello. ECH encrypts the sensitive inner ClientHello -- including the server name a censor would want to read -- to the server's HPKE public key, published in an ECHConfig and fetched ahead of time. It uses SetupBaseS on the client and SetupBaseR on the server [7]. Currency pin: ECH is now Standards-Track RFC 9849, published in March 2026 by Rescorla, Oku, Sullivan, and Wood, and formerly known as draft-ietf-tls-esni. It is an RFC, not a draft, and its text references HPKE throughout [7].

Messaging Layer Security. MLS uses HPKE inside TreeKEM: when a group member updates the ratchet tree, it encrypts the new path secrets to the public keys of the relevant tree nodes, using SealBase [23]. Group-scale forward secrecy and post-compromise security come from the tree and the ratchet, not from HPKE.

Oblivious HTTP and Oblivious DNS. Both split trust between a relay and a gateway so that no single party sees both who you are and what you asked. The client seals its request to the gateway's HPKE key; the relay forwards ciphertext it cannot read; the gateway decapsulates and, crucially, uses Export to key the response rather than looping Seal [9], [24]. Oblivious DNS, in 2022, was the first shipped RFC deployment of HPKE.

Ctrl + scroll to zoom
Where HPKE sits in an Oblivious HTTP relay and gateway split: the relay sees the client but not the content, the gateway sees the content but not the client

Privacy Pass and its Private Access Tokens belong on the list too, at one remove: some deployments run token issuance over Oblivious HTTP (RFC 9578 does not require it), and therefore over HPKE [25], [9].

Two observations reinforce the article's spine. First, every marquee deployment uses mode_base: sender authentication and forward secrecy are supplied at the protocol layer -- by the TLS handshake, by the MLS ratchet -- leaving HPKE to be the boring sealing primitive it was designed to be. Second, Oblivious HTTP and Oblivious DNS reach for Export to key their own framing instead of looping Seal, exactly the extensibility the export interface was built for. The deployments did not stretch HPKE; they used the parts the design anticipated.

HPKE won the IETF privacy stack. But "won" invites the question a careful engineer actually asks: won against what, and when would I still reach for something else? The honest comparison comes next -- along with the one construction people keep miscategorizing as HPKE.

9. Competing Approaches, and One That Is Not HPKE

HPKE is the right default for new protocol design, but it is not the only encrypt-to-a-public-key tool, and it is not always the right one. Because all of these building blocks cost about the same -- roughly two elliptic-curve scalar multiplications for the sender, plus an ephemeral value and an AEAD tag of overhead -- the honest comparison is not speed. It is security model, agility, and proof discipline.

ToolConstructionAgility and modesProof and shared vectorsBest suited for
HPKE (RFC 9180)KEM-DEM (DHKEM + KDF + AEAD)Full agility; four modesMachine-checked; per-suite vectorsNew protocol design
libsodium crypto_box / sealFixed X25519 + XSalsa20-Poly1305None; mutual-auth, repudiableNo mechanized proof; partial vectorsBoth ends yours, zero knobs
JOSE ECDH-ES (RFC 7518)Ephemeral-static ECDH + Concat KDFSome agility; encryption onlyNo mechanized proof; partial vectorsJWE and JWT stacks
ageX25519 + HKDF-SHA256 + ChaCha20-Poly1305None; file-orientedNo mechanized proof; has vectorsEncrypting files to a key
Raw ECIESEphemeral-static ECDH + Encrypt-then-MACPer-standard knobsProof gaps; no shared vectorsLegacy and blockchain interop

libsodium crypto_box and sealed boxes. One fixed suite -- X25519, XSalsa20, Poly1305 -- simple and misuse-resistant, but with no agility, no formal mode menu, and no HPKE-grade test vectors; raw crypto_box also makes the caller manage nonces, and it is deliberately repudiable and mutually authenticated [32]. Reach for it when you own both ends and want zero knobs.

JOSE and COSE ECDH-ES. Ephemeral-static ECDH with the Concat KDF, native to JWE and JWT [33]. It has no auth or psk modes, uses Concat-KDF rather than HKDF, and ships nothing like HPKE's vectors. It is chosen by platform gravity, and a COSE-HPKE binding is emerging to bring HPKE into that world [34].

age. Wraps a random file key to X25519 recipients with HKDF-SHA256 and ChaCha20-Poly1305 -- the same primitive family as DHKEM(X25519) plus an AEAD, but fixed and file-oriented [35]. Reach for it to encrypt files to a public key.

Raw ECIES survives only for interop with existing deployments, such as blockchain wallets and legacy PKI [1]. And RSA-KEM (RFC 5990) is worth one line: it proves the KEM-DEM pattern generalizes beyond Diffie-Hellman, but RFC 9180 defines no RSA-KEM ciphersuite, so it stays a niche [36].

Now the boundary marker, because it is the single most common miscategorization in this space.

The ranking has a pattern. The fixed-suite tools win on simplicity, ECDH-ES wins on platform gravity, and everything else loses to HPKE for new design, because HPKE has the agility, the modes, the proofs, and the vectors. That makes HPKE sound like a strict upgrade. It is not. Its power comes from a set of things it deliberately refuses to do -- and mistaking those refusals for oversights is where every real HPKE incident begins.

10. The Refusals That Look Like Features

HPKE's non-goals are not gaps someone forgot to fill. Several are impossibilities for a single-shot public-key primitive, and RFC 9180 states them plainly so that no one mistakes the boundary for a bug. Knowing them is the entire discipline this article is teaching.

No forward secrecy against recipient compromise, in any mode. RFC 9180 is explicit: HPKE ciphertexts "are not forward secret with respect to recipient compromise in any mode," because only long-term secrets are used on the recipient's side [3]. This is structural, not an oversight.

Forward secrecy

The property that compromising a long-term key does not expose past messages. HPKE has none with respect to recipient compromise: because a single-shot ciphertext is encrypted to a static recipient key, anyone who later obtains that key can decrypt every ciphertext ever sent to it. Forward secrecy must come from a ratchet or key rotation outside HPKE -- which is exactly why MLS and TLS layer it on top.

auth mode is not a signature. As the modes section showed, the authenticated modes are signcryption with insider and outsider guarantees, not third-party non-repudiation. The recipient who shares the Diffie-Hellman secret could have produced the same ciphertext, so it cannot prove authorship to anyone else [5], [3]. If you need a portable proof of who signed, use a digital signature.

Not key-committing by default. HPKE's AEADs bind a ciphertext to a plaintext, but not to a unique key.

Key commitment (committing AEAD)

A property by which a ciphertext can be opened successfully under exactly one key. HPKE's standard AEADs are not key-committing by default, which enables a partitioning-oracle attack: an attacker who can submit ciphertexts and observe success or failure can binary-search a low-entropy secret. The eventual fix is a committing AEAD, whose properties are codified in RFC 9771 [38].

The concrete danger is a low-entropy PSK. RFC 9180, applying the partitioning-oracle result of Len, Grubbs, and Ristenpart, notes that an attacker can recover a PSK drawn from a set of size mkm \cdot k in roughly m+logkm + \log k oracle queries [3], [39]. That logarithmic search is precisely why the PSK requirement is a floor on entropy, at least 32 bytes, and not a note about password strength. The committing-AEAD countermeasure is standardized as a set of properties in RFC 9771, awaiting a committing aead_id [38].

No replay, reordering, downgrade, or metadata protection. HPKE does not detect a replayed ciphertext, enforce message ordering across independent contexts, prevent ciphersuite downgrade, or hide the length or existence of a message. Every one of these is deliberately left to the surrounding protocol [3].

Classical-only by default. This is the single primitive-level gap -- and, as the deployment section showed, the only non-goal you close by swapping a component rather than redesigning the scheme.

It is worth being precise about what is and is not finished. The core security question is closed: sufficiency and necessity together establish that the KEM-DEM composite reaches IND-CCA2 exactly when its halves do, and all four modes have been machine-checked [10], [11], [4], [5], [21].

The residual gaps are about model and API coverage, not the core: the tightest authenticated-mode proof covers single-shot use rather than many messages, some results live in the random-oracle model rather than the standard model, and the post-quantum composition result is tightest for the authenticated mode, with the psk and auth_psk modes since covered by the PSK-modes analysis and a fully general standard-model post-quantum proof across all four modes still open [5], [21]. Even the definition of IND-CCA for KEMs has known subtleties the analyses had to navigate [3], [40].

The math is finished; the risk moved to the interface. HPKE has no known catastrophic break, so its hazards are misuse of its four modes and its non-goals -- and the post-quantum swap, which changes the KEM and not the scheme, is the proof that the architecture was the achievement. The discipline is knowing what HPKE deliberately does not do.

Every non-goal above is a sentence a designer might wrongly assume was a yes. So the honest failure catalog for HPKE is not a list of broken math -- there is none -- it is a list of those wrong assumptions, made in real code. Here they are, named.

11. The Failure and Misuse Catalog

HPKE itself has no known catastrophic real-world break, and that absence is the finding, not a gap in the research. This is a deliberately hedged absence-of-evidence claim: it rests on three machine-checked analyses and on the public advisories to date being implementation bugs, not design flaws -- never a claim that HPKE is "proven secure" for all time [4], [5], [21]. The catalog is two things: the predecessor ECIES defects HPKE fixed, and the misuse patterns that appear when a non-goal is assumed away.

MisuseRoot causeNon-goal assumed awayFix or lesson
Sequence counter overflowFixed-width counter wraps, nonce repeatsAEAD nonce uniqueness is the caller's jobWide counter; single-shot; the hpke-rs u64 fix
Static-key forward-secrecy illusionExpecting past secrecy from a static keyNo FS against recipient compromiseRatchet or rotate keys above HPKE
Low-entropy PSKGuessable PSK enables partitioning oracleAEADs are not key-committingPSK with at least 32 bytes of entropy
Bad ephemeral randomnessWeak CSPRNG in EncapHPKE assumes good randomnessUse a vetted CSPRNG
auth treated as a signatureInsider can forge to a third partyauth is signcryption, not non-repudiationUse a digital signature for authorship
Out-of-order or reused OpenOrdering not enforced by HPKENo reordering or replay protectionSeal in order; put ordering in the AAD
Export-only misuseCalling Seal on an export-only suiteInterface contract not enforcedError, do not truncate; the hpke-rs fix

The first advisory: hpke-rs, GHSA-g433-pq76-6cmf. In February 2026 the hpke-rs and hpke-rs-rust-crypto crates received a high-severity advisory bundling four fixes, and the reason it vindicates this article's spine rather than refuting it is that every fix maps onto a non-goal RFC 9180 had already flagged [6], [3], [41]:

  • Sequence-number overflow leading to nonce reuse. The context counter could overflow; the fix widened it to a 64-bit integer. This is exactly the scenario the earlier structural model wraps in miniature, and exactly the AEAD nonce-uniqueness hazard the usage section warned about. It was reported by Kobeissi.
  • Export-only misuse and silent truncation. Calling Seal/Open on an export-only suite, and requesting an over-long Export, previously truncated silently; both now return errors. Reported by Arciszewski.
  • Missing X25519 zero and low-order key check. The library did not reject all-zero keys; it now errors on them, closing a small-subgroup foot-gun.
  • A post-quantum KEM-identifier mapping bug. A confusion in the draft X-Wing KEM-id mapping -- harmless in effect, but a foretaste of the ciphersuite-management care the post-quantum transition will demand.

The second advisory: @hpke/core / hpke-js, CVE-2025-64767. Three months earlier, in November 2025, the @hpke/core package -- the core of the hpke-js library in the survey below -- drew a critical advisory (GHSA-73g8-5h73-26h4, CVSS 9.1) for reusing AEAD nonces. The root cause was a concurrency race rather than a counter overflow: the public SenderContext Seal() API let two concurrent asynchronous seal() calls read the same sequence number, so both derived the same nonce and reused it in the AEAD, forfeiting confidentiality and integrity outright. Versions @hpke/core 1.7.4 and earlier are affected; the fix shipped in 1.7.5. The lesson is the misuse table's first row reached from a new direction -- nonce uniqueness is the caller's job -- so a single Context must never have Seal called on it concurrently: serialize per-context sealing, or prefer single-shot. It is a second, independent instance of the exact nonce-reuse hazard the usage section named, arrived at by a race instead of an overflow [42], [3], [43].

Notice what is not on either advisory: no broken cipher, no discrete-log break, no flaw in the KEM-DEM composition -- only dropped or mishandled checks and a concurrency race, each mapping onto a non-goal or requirement the specification had already named.

The rest of the catalog is misuse in application code, and it maps onto the table above. Expecting forward secrecy from a static recipient key yields none, in any mode [3]. A low-entropy PSK turns mode_psk into a partitioning oracle, so the PSK needs at least 32 bytes of entropy [3], [39]. Bad ephemeral randomness can collapse base-mode confidentiality outright, because a predictable ephemeral key undoes the encapsulation.

Treating auth as a signature walks into the insider-versus-outsider trap [5], and out-of-order Open or context reuse breaks because applications, not HPKE, must preserve Seal order and put ordering in the associated data [3]. The predecessor ECIES defects -- benign malleability, missing proofs, missing test vectors -- round out the list, each already closed by the four design decisions.

HPKE has no catastrophic break because the risk moved up a layer. Master the four modes and the non-goals and you have a boring, safe default. The math held; the risk moved to the interface.

Read the catalog back to front and a single pattern falls out: every entry is a non-goal treated as a goal, or an implementation dropping a check the specification demanded. None is a broken cipher. That means the "how to use HPKE safely" guide is not a research problem. It is the non-goals, turned into rules.

12. A Practical Decision Guide

Everything above collapses into a short decision procedure and a shorter list of things never to do.

Ctrl + scroll to zoom
The 2026 decision flow: pick a building block, then an HPKE mode, from a stranger's public key to a shipped configuration

Which building block? A new protocol or standard that needs agility, proofs, and modes takes HPKE. Both ends under your control with no options wanted takes a libsodium sealed box. The JWE and JWT world takes ECDH-ES, or COSE-HPKE as it matures [34]. Encrypting files takes age. Interop with an existing ECIES deployment takes a matched ECIES configuration and nothing new [3].

Which HPKE mode? Only the recipient's public key in hand: mode_base, the deployment default. A pre-shared secret already exists: mode_psk, with a PSK carrying at least 32 bytes of entropy. The recipient must know that a specific sender key sealed the message: mode_auth. Both a sender identity and a shared secret: mode_auth_psk. Never reach for an authenticated mode expecting a third-party-verifiable signature.

Which ciphersuite? The interop-safe classical default is DHKEM(X25519, HKDF-SHA256) with HKDF-SHA256 and AES-128-GCM, substituting ChaCha20-Poly1305 where AES is unaccelerated. If you only ever export, use the export-only AEAD (0xFFFF). If you are planning for quantum resistance, track the X-Wing and ML-KEM drafts, budget roughly 1.1 KB per encapsulation, and remember that the post-quantum suites give you base and psk but not yet auth [28].

A worked path to shipping. Start with a single-shot Seal/Open for one message, which removes the entire counter-management problem. Reach for Export only when you are keying another protocol's framing. And before you trust your integration, reproduce one of RFC 9180's published test vectors end to end: if your library produces the specified bytes, you have proof it interoperates -- the very check the ECIES era could not run. The mature libraries below make this easy.

LibraryLanguageNotes
OpenSSL 3.2+CNative HPKE via OSSL_HPKE_CTX [44], [45]
BoringSSLCHPKE used in the Chrome and ECH stack [46]
Cloudflare CIRCLGoEarly production HPKE; powers OHTTP and ODoH [47]
rust-hpkeRustRFC 9180-conformant crate [48]
hpke-rsRustFormally oriented; ships a draft X-Wing KEM id [41]
hpke-jsJavaScriptBrowser, Node.js, and Deno; full RFC 9180 suites; require @hpke/core 1.7.5 or later (fixes CVE-2025-64767) [43], [42]

The list of nevers is short and worth memorizing: never expect forward secrecy from a static recipient key; never treat auth as a signature; never reuse a Context across independent streams or let the sequence counter overflow; never use a low-entropy PSK; never open one ciphertext under multiple keys and assume it committed to one; and never skip your library's test-vector check.

Why reproducing a test vector is the highest-value five minutes you will spend

Run your library against RFC 9180's Appendix A vectors for the exact ciphersuite you ship. A mismatch on the first byte tells you -- before any traffic flows -- that your point encoding, your info handling, or your KDF wiring disagrees with the standard. This is the single check the four ECIES standards could never offer, and it is the reason "conformant" now implies "interoperable."

The checklist is short because the lesson is one sentence. Before restating it, clear away the confident-but-wrong beliefs that keep regenerating the misuse catalog in code review.

13. The Misconceptions, Corrected

HPKE gets misused mostly because a handful of confident, wrong sentences keep getting said in design meetings. Here they are, corrected.

Frequently asked questions about HPKE

Is HPKE forward-secret?

No -- not with respect to recipient compromise, in any mode. RFC 9180 states that HPKE ciphertexts are not forward secret against recipient compromise, because only long-term secrets are used on the recipient's side [3]. Forward secrecy has to come from a ratchet or key rotation layered above HPKE, which is exactly what MLS and TLS do.

Is auth mode a digital signature?

No. auth mode is signcryption, not a third-party-verifiable signature: the recipient cannot prove authorship to anyone else, for the reason the modes section works through. If you need portable proof of who signed, use a digital signature [5].

Can I reuse a Context, and how many messages can one seal?

A Context is stateful: an incrementing sequence number derives each per-message nonce. Seal strictly in order, never reuse a context across independent streams, respect the per-ciphersuite AEAD message-count limit, and never let the sequence counter overflow -- any of those repeats a nonce, which is catastrophic [3]. For independent messages, prefer single-shot Seal/Open and avoid the bookkeeping entirely.

Is HPKE post-quantum?

Not by default -- DHKEM is classical-only. The migration path is to drop a hybrid KEM such as X-Wing (X25519 with ML-KEM-768) into the KEM slot, secure if either component holds [8], [27]. Those suites are still draft-stage, add roughly 1.1 KB per encapsulation, and are not authenticated KEMs yet, so they cover base and psk but not auth [28].

HPKE or libsodium crypto_box?

crypto_box is one fixed suite (X25519, XSalsa20, Poly1305), deliberately repudiable and mutually authenticated, with manual nonce handling for the raw API [32]. It is excellent when you own both ends and want no options. HPKE wins for new protocol design, because it brings algorithm agility, four formal modes, machine-checked proofs, and test vectors [3].

Does iMessage PQ3 use HPKE?

No -- PQ3 is KEM-based but not HPKE; it is a ratcheted, interactive scheme rather than single-shot encryption, as the earlier PQ3 aside details [37]. Do not list it as an HPKE deployment.

Do I need to worry about partitioning oracles and key commitment?

Only if you use low-entropy pre-shared keys or open one ciphertext under multiple candidate keys. HPKE's AEADs are not key-committing by default, which is why a weak PSK enables a partitioning oracle; keep the PSK entropy at 32 bytes or more [3], [39]. A committing AEAD, whose properties are codified in RFC 9771, may eventually arrive as a new aead_id [38].

Every correction points at the same root: the primitive was never the risk. It is time to say the sentence the whole article was built to earn.

14. The Math Held; The Risk Moved to the Interface

Return to where this started: an elementary task -- encrypt to a public key -- that the industry got subtly wrong four incompatible times, with no proofs and no shared test vectors, one standard shrugging off a second valid ciphertext as "benign" [1], [2]. HPKE is what that task looks like when it is finally done right.

The evidence has accumulated into a single claim. HPKE is one KEM-DEM construction: a Key Encapsulation Mechanism generates a shared secret and its encapsulation, an HKDF key schedule with every public key bound in derives the working keys, and an AEAD seals the payload.

It is proven secure against adaptive chosen-ciphertext attack under standard assumptions, machine-checked across all four modes, test-vectored per ciphersuite, and it designs benign malleability out by making the public-key binding mandatory rather than optional [3], [10], [4], [5]. The consequence is the whole thesis: HPKE has no known catastrophic break, so the discipline it demands is not "how do I make it secure" but "do I know its four modes and its non-goals?"

Every entry in the misuse catalog proved the point. Forward secrecy expected from a static key, a signature expected from auth mode, safety expected from a non-committing AEAD, a fresh nonce expected from an overflowed counter -- each was a non-goal assumed away, not a broken cipher [3], [6].

And the post-quantum migration is the architecture's final vindication: the one primitive-level gap, quantum resistance, is closed by swapping the KEM for a hybrid like X-Wing, not by redesigning the scheme [8]. That is the exact payoff of having abstracted the KEM in the first place. It echoes this series' running lesson from the KEM-DEM and digital-signature parts: the math held, and the interface is where the work -- and the risk -- actually lives.

So carry one question out of this article and apply it to any encrypt-to-a-public-key design you review.

Which of the four modes is this, and which of HPKE's non-goals am I about to assume it has?

The primitive was never the weak link, and the post-quantum swap shows it never will be. That is why this is Part 19 of a field guide to protocol design, not a chapter about any one cipher. HPKE is the clean seam where the primitives of the earlier parts -- authenticated encryption, key derivation, elliptic curves, KEM-DEM, signatures -- become a protocol. The math is finished, the vectors are published, and the only thing left to get wrong is you.

Study guide

Key terms

HPKE
The RFC 9180 standard for encrypting to a public key, built from a KEM, a KDF, and an AEAD named together as a ciphersuite.
KEM
A key encapsulation mechanism: Encap generates a fresh shared secret and its encapsulation from the recipient's public key; you never choose the key.
DEM
The data encapsulation mechanism: an AEAD that bulk-encrypts the payload under a key derived from the KEM's shared secret.
DHKEM
HPKE's Diffie-Hellman KEM, using an ephemeral-static exchange and binding the encapsulation and recipient key into the derivation.
Key schedule
HPKE's HKDF process: Extract a pseudorandom key from the shared secret, then Expand it over mode, psk_id_hash, and info_hash to yield the AEAD key, base_nonce, and exporter_secret.
AEAD
Authenticated encryption with associated data: confidentiality and integrity in one primitive; the DEM half of HPKE.
IND-CCA2
Security against adaptive chosen-ciphertext attack; the bar HPKE meets and that benign malleability violates.
Benign malleability
The ECIES defect where an attacker forges a second valid ciphertext for the same plaintext; HPKE kills it by binding all public keys into the key schedule.
Signcryption
Combined encryption and sender authentication with insider/outsider guarantees; HPKE's auth modes are signcryption, not third-party signatures.
Key commitment
An AEAD property binding a ciphertext to one key; HPKE is not committing by default, enabling partitioning oracles against low-entropy PSKs.

References

  1. SECG (2009). SEC 1: Elliptic Curve Cryptography, Version 2.0. https://secg.org/sec1-v2.pdf
  2. V. Gayoso Martinez, F. Hernandez Alvarez, L. Hernandez Encinas, & C. Sanchez Avila (2010). A Comparison of the Standardized Versions of ECIES. https://ieeexplore.ieee.org/abstract/document/5604194/
  3. Richard Barnes, Karthikeyan Bhargavan, Benjamin Lipp, & Christopher A. Wood (2022). RFC 9180: Hybrid Public Key Encryption. https://www.rfc-editor.org/rfc/rfc9180.txt
  4. Benjamin Lipp (2020). An Analysis of Hybrid Public Key Encryption. https://eprint.iacr.org/2020/243
  5. Joel Alwen, Bruno Blanchet, Eduard Hauck, Eike Kiltz, Benjamin Lipp, & Doreen Riepel (2020). Analysing the HPKE Standard. https://eprint.iacr.org/2020/1499
  6. GitHub Security Advisory Database (2026). GHSA-g433-pq76-6cmf: hpke-rs context-counter overflow and related fixes. https://github.com/advisories/GHSA-g433-pq76-6cmf
  7. Eric Rescorla, Kazuho Oku, Nick Sullivan, & Christopher A. Wood (2026). RFC 9849: TLS Encrypted Client Hello. https://www.rfc-editor.org/rfc/rfc9849.txt
  8. Manuel Barbosa, Deirdre Connolly, Joao Diogo Duarte, Aaron Kaiser, Peter Schwabe, Karoline Varner, & Bas Westerbaan (2024). X-Wing: The Hybrid KEM. https://eprint.iacr.org/2024/039
  9. Martin Thomson & Christopher A. Wood (2024). RFC 9458: Oblivious HTTP. https://www.rfc-editor.org/rfc/rfc9458.txt
  10. Ronald Cramer & Victor Shoup (2001). Design and Analysis of Practical Public-Key Encryption Schemes Secure against Adaptive Chosen Ciphertext Attack. https://eprint.iacr.org/2001/108
  11. Javier Herranz, Dennis Hofheinz, & Eike Kiltz (2006). Some (in)sufficient Conditions for Secure Hybrid Encryption. https://eprint.iacr.org/2006/265
  12. Whitfield Diffie & Martin E. Hellman (1976). New Directions in Cryptography. https://doi.org/10.1109/TIT.1976.1055638
  13. John Linn (1993). RFC 1421: Privacy Enhancement for Internet Electronic Mail, Part I. https://www.rfc-editor.org/info/rfc1421
  14. Taher ElGamal (1985). A Public Key Cryptosystem and a Signature Scheme Based on Discrete Logarithms. https://doi.org/10.1109/TIT.1985.1057074
  15. Kathleen Moriarty, Burt Kaliski, Jakob Jonsson, & Andreas Rusch (2016). RFC 8017: PKCS #1: RSA Cryptography Specifications Version 2.2. https://www.rfc-editor.org/info/rfc8017/
  16. Michel Abdalla, Mihir Bellare, & Phillip Rogaway (1999). DHAES: An Encryption Scheme Based on the Diffie-Hellman Problem. https://eprint.iacr.org/1999/007
  17. Joel Alwen, Jonas Janneck, Eike Kiltz, & Benjamin Lipp (2023). The Pre-Shared Key Modes of HPKE. https://eprint.iacr.org/2023/1480
  18. Alexander W. Dent & Yuliang Zheng (2010). Practical Signcryption. https://doi.org/10.1007/978-3-540-89411-7
  19. Richard Barnes, Benjamin Beurdouche, Raphael Robert, Jon Millican, Emad Omara, & Katriel Cohn-Gordon (2023). RFC 9420: The Messaging Layer Security (MLS) Protocol. https://www.rfc-editor.org/rfc/rfc9420.txt
  20. Eric Kinnear, Patrick McManus, Tommy Pauly, Tanya Verma, & Christopher A. Wood (2022). RFC 9230: Oblivious DNS over HTTPS (ODoH). https://www.rfc-editor.org/rfc/rfc9230.txt
  21. Sofia Celi, Alex Davidson, Steven Valdez, & Christopher A. Wood (2024). RFC 9578: Privacy Pass Issuance Protocols. https://www.rfc-editor.org/rfc/rfc9578.html
  22. NIST (2024). FIPS 203: Module-Lattice-Based Key-Encapsulation Mechanism Standard (ML-KEM). https://csrc.nist.gov/pubs/fips/203/final
  23. Daniel J. Bernstein, Tanja Lange, & Peter Schwabe NaCl: Networking and Cryptography Library -- crypto_box. https://nacl.cr.yp.to/box.html
  24. Michael B. Jones (2015). RFC 7518: JSON Web Algorithms (JWA). https://www.rfc-editor.org/rfc/rfc7518.txt
  25. Filippo Valsorda & Ben Cartwright-Cox The age File Encryption Format (C2SP). https://github.com/C2SP/C2SP/blob/main/age.md
  26. Jim Randall, Burt Kaliski, John Brainard, & Sean Turner (2010). RFC 5990: Use of the RSA-KEM Key Transport Algorithm in the CMS. https://www.rfc-editor.org/rfc/rfc5990.txt
  27. Julia Len, Paul Grubbs, & Thomas Ristenpart (2021). Partitioning Oracle Attacks. https://eprint.iacr.org/2020/1491
  28. Mihir Bellare, Dennis Hofheinz, & Eike Kiltz (2009). Subtleties in the Definition of IND-CCA: When and How Should Challenge Decryption Be Disallowed?. https://eprint.iacr.org/2009/418
  29. Google BoringSSL. https://github.com/google/boringssl
  30. Cloudflare Encrypt It or Lose It: Hybrid Public Key Encryption (CIRCL). https://blog.cloudflare.com/hybrid-public-key-encryption/
  31. Michel Abdalla, Mihir Bellare, & Phillip Rogaway (2001). DHIES: An Encryption Scheme Based on the Diffie-Hellman Assumption (manuscript). https://web.cs.ucdavis.edu/~rogaway/papers/dhies.html
  32. Hanno Bock, Juraj Somorovsky, & Craig Young (2017). ROBOT Attack: Return Of Bleichenbacher's Oracle Threat. https://robotattack.org/
  33. Hanno Bock, Juraj Somorovsky, & Craig Young (2017). Return Of Bleichenbacher's Oracle Threat (ROBOT). https://eprint.iacr.org/2017/1189
  34. Deirdre Connolly, Peter Schwabe, & Bas Westerbaan X-Wing: general-purpose hybrid post-quantum KEM (draft-connolly-cfrg-xwing-kem). https://datatracker.ietf.org/doc/draft-connolly-cfrg-xwing-kem/
  35. Deirdre Connolly, Peter Schwabe, & Bas Westerbaan (2024). X-Wing: general-purpose hybrid post-quantum KEM (draft-connolly-cfrg-xwing-kem-06). https://www.ietf.org/archive/id/draft-connolly-cfrg-xwing-kem-06.html
  36. Richard Barnes, Karthikeyan Bhargavan, Benjamin Lipp, & Christopher A. Wood Hybrid Public Key Encryption (CFRG draft lineage, datatracker). https://datatracker.ietf.org/doc/draft-irtf-cfrg-hpke/
  37. Alexander Bozhko (2025). RFC 9771: Properties of Authenticated Encryption with Associated Data (AEAD) Algorithms. https://www.rfc-editor.org/rfc/rfc9771.txt
  38. Deirdre Connolly ML-KEM KEM Options for HPKE (draft-connolly-cfrg-hpke-mlkem). https://datatracker.ietf.org/doc/draft-connolly-cfrg-hpke-mlkem/
  39. Deirdre Connolly, Richard Barnes, & Paul Grubbs Hybrid KEMs (draft-irtf-cfrg-hybrid-kems). https://datatracker.ietf.org/doc/draft-irtf-cfrg-hybrid-kems/
  40. Richard Barnes Post-Quantum KEMs for HPKE (draft-barnes-hpke-pq). https://datatracker.ietf.org/doc/draft-barnes-hpke-pq/
  41. GitHub Security Advisory Database (2025). GHSA-73g8-5h73-26h4 (CVE-2025-64767): @hpke/core reuses AEAD nonces. https://github.com/advisories/GHSA-73g8-5h73-26h4
  42. OpenSSL Project (2023). Hybrid Public Key Encryption in OpenSSL 3.2. https://mirror.openssl-library.org/post/2023-10-18-ossl-hpke/
  43. OpenSSL Project OSSL_HPKE_CTX_new(3) Manual Page. https://www.openssl.org/docs/manmaster/man3/OSSL_HPKE_CTX_new.html
  44. Michael Rosenberg rust-hpke: An Implementation of RFC 9180 in Rust. https://github.com/rozbb/rust-hpke
  45. Cryspen hpke-rs: HPKE Implementation in Rust. https://github.com/cryspen/hpke-rs
  46. Ajitomi Daisuke hpke-js: HPKE for Browsers, Node.js, and Deno. https://github.com/dajiaji/hpke-js
  47. Apple Security Engineering and Architecture (2024). iMessage with PQ3: The New State of the Art in Quantum-Secure Messaging at Scale. https://security.apple.com/blog/imessage-pq3/
  48. Hannes Tschofenig, Michael B. Jones, Orie Steele, Daisuke Ajitomi, & Laurence Lundblade Use of Hybrid Public-Key Encryption (HPKE) with COSE (draft-ietf-cose-hpke). https://datatracker.ietf.org/doc/draft-ietf-cose-hpke/