Chapter 5: KEMs vs key agreement vs public-key encryption
KEMs, key agreement, and public-key encryption solve three different problems. A key-agreement protocol such as Diffie-Hellman lets two parties who share no secret derive one from messages an eavesdropper can read, and both parties contribute randomness to it. A public-key encryption (PKE) scheme lets a sender deliver a specific message, for example a key the sender already chose, that only the holder of the private key can read. A key-encapsulation mechanism (KEM) has a different interface. Its encapsulation algorithm takes the receiver’s public key and outputs a shared key together with a ciphertext. Unlike encryption, it takes no message from the caller. The receiver recovers the same key from the ciphertext with its private key. Post-quantum key establishment is standardized in this third form: NIST published ML-KEM directly as a KEM (National Institute of Standards and Technology, 2025b).
The chapter’s second question is what makes the last two secure, and correct decryption is not the answer. Textbook RSA decrypts every ciphertext correctly, yet an attacker can test guesses at its plaintext using only the public key. A randomized scheme built from this chapter’s own Diffie-Hellman resists that test and still gives its plaintext away to a different attacker. What separates the two attackers is what they can do. An eavesdropper only reads ciphertexts. An active attacker can also send ciphertexts of its own to the receiver and learn something from the result, and one such query breaks the Diffie-Hellman-based scheme, which no known classical eavesdropping attack breaks at real sizes.
The chapter writes both attackers down as security definitions and then builds the standard fix for the second, the Fujisaki-Okamoto transform. It takes an encryption scheme that resists eavesdroppers and produces a KEM that also resists the active attacker, by letting the receiver detect any ciphertext that is not what an honest sender would have produced for its own plaintext. ML-KEM is built this way, and Chapter 11 builds ML-KEM.
A shared key without a pre-shared secret
Section titled “A shared key without a pre-shared secret”The canonical motivating scenario is two parties who have never met and want a fresh symmetric key, say a 256-bit key for the rest of the session. Alice and Bob can see each other’s public values over an authenticated channel but share no prior secret. The 1976 paper of Diffie and Hellman gave the first published solution to this problem (Diffie & Hellman, 1976). Fix a prime and a generator of the multiplicative group . Alice picks a random exponent and sends . Bob picks a random exponent and sends . Each side then computes the shared value from the other side’s public value:
Substituting and gives , so both sides derive the same element of the group. The whole exchange runs on a toy prime in a few lines of Python:
# Toy Diffie-Hellman in (Z/pZ)^* with p = 23 and g = 5.# g is a primitive root mod 23, so ord(g) = phi(23) = 22.p = 23g = 5
# Alice picks a secret exponent and publishes A.a = 6A = pow(g, a, p)
# Bob picks a secret exponent and publishes B.b = 15B = pow(g, b, p)
# Each party computes the shared value from the other's public value.alice_shared = pow(B, a, p)bob_shared = pow(A, b, p)print(A, B)print(alice_shared == bob_shared, alice_shared)# ==> 8 19# ==> True 2Every Python block this chapter prints is also a standalone file in the companion repository, under chapter-code/ch05/, one file per block. Appendix C covers the clone and the environment they run on.
The shared value is not yet a usable AES key, since a group element is not a uniform byte string, so real deployments feed it through a key-derivation function (for example HKDF-Extract-then-Expand with SHA-256) to produce the symmetric key.
Now flip the question. Suppose Alice already has a 256-bit key that she chose herself, and she wants to send it to Bob. Diffie-Hellman cannot do this. Alice has no way to force the shared output to equal a specific she chose. Public-key encryption can. Alice encrypts under Bob’s public key and sends the ciphertext. But if Alice only wants Bob to end up with any fresh random key that Alice also knows, public-key encryption is overkill. Alice does not need to choose ; she only needs some random that Bob can recover. That is the KEM problem, and it is the primitive the rest of the chapter develops.
Three interfaces
Section titled “Three interfaces”A two-party key-agreement protocol is a pair of interactive algorithms . Alice runs and Bob runs . Each party sends one or more public messages, and at the end both parties output a shared secret of some fixed bit length . For Diffie-Hellman the two messages are and , and both parties output where is a key-derivation function. Security is stated against an adversary that reads both public messages but cannot actively interfere. Without an authenticated channel the scheme is vulnerable to man-in-the-middle, which a later section walks.
The post-quantum version of this story does not replace the math of Diffie-Hellman directly. Shor’s algorithm breaks the discrete logarithm in and on elliptic curves (Shor, 1994), so every group that worked for pre-quantum Diffie-Hellman fails. Deployed post-quantum cryptography replaces classical Diffie-Hellman with a KEM rather than another interactive key-agreement protocol, though early lattice proposals (NewHope, Frodo) did start from the interactive framing.
A public-key encryption scheme is a triple . Gen() samples a keypair. Enc(pk, m) produces a ciphertext . Dec(sk, c) returns the plaintext or a rejection symbol. Correctness asks that decrypting an honest encryption of return , except with small probability. Textbook RSA from Chapter 4 is the deterministic function .
A KEM is a triple . Gen() samples a keypair . Encap(pk) uses fresh randomness to produce a ciphertext and a shared key , and returns the pair . The ciphertext travels over the wire, and both parties use as the shared symmetric key. Decap(sk, c) returns . What it returns for a ciphertext it rejects differs between schemes, and the Fujisaki-Okamoto section below takes that up. The conceptual split between a KEM and a PKE is exactly this: the caller of Encap supplies no key, and the encapsulation routine derives from fresh randomness. In a PKE, the sender chooses the plaintext. Boneh and Shoup give the abstract KEM/DEM decomposition and its security game (Boneh & Shoup, 2023).
encap(pk) on Alice's side produces (ct, K), and Bob's decap(sk, ct) recovers the same K. NIST SP 800-227 notes that a KEM can be viewed as key transport or as key agreement depending on its construction, and places ML-KEM on the key-agreement reading, so "establishment" is the safer word for the general picture (National Institute of Standards and Technology, 2025b). The DEM handles message confidentiality with K. In a direct KEM/DEM construction the DEM is an authenticated encryption with associated data (AEAD) construction such as AES-GCM or ChaCha20-Poly1305, so a tampered symmetric ciphertext fails its integrity check. In a protocol such as TLS 1.3 there is no standalone DEM: K feeds a key schedule that derives the AEAD traffic keys, and the AEAD rather than the schedule is what rejects tampering.KEM correctness asks that the legitimate decapsulation almost always return the same key that encapsulation produced. A KEM is -correct if
where is a negligible function of the security parameter. Note which randomness the probability runs over: the key generation as well as the encapsulation. That is weaker than a per-key guarantee, and deliberately so: the published average does not imply a bound for every generated keypair, so quantifying over every honestly generated keypair would claim more than the figures below support. For lattice-based KEMs like ML-KEM the error is not exactly zero, because the underlying LWE decryption can fail on rare noise samples. FIPS 203 reports decapsulation-failure estimates of approximately for ML-KEM-512, for ML-KEM-768, and for ML-KEM-1024 (National Institute of Standards and Technology, 2024). Chapter 11 develops the centered-binomial noise budget these rates come out of, and takes the exact figures from the same FIPS 203 analysis rather than re-deriving them.
Correct is not secure
Section titled “Correct is not secure”Textbook RSA can be wrapped as a toy KEM. Encapsulation samples a random in and sets the ciphertext to . Decapsulation is , which is the same computation as textbook RSA decryption. The snippet below builds the toy KEM on the 64-bit modulus from Chapter 4 and verifies a single round-trip:
# Toy RSA-KEM built on the 64-bit textbook RSA from Chapter 4.# Encap samples a random K in [1, n - 1] and encrypts it as K^e mod n.# Decap runs raw RSA decryption to recover the same K.# Demo only: random.Random is deterministic and NOT cryptographically secure.# Real encapsulation uses secrets.SystemRandom and feeds the output through# a KDF to produce a fixed-length symmetric key.import random
p = 3184935163q = 3199286161n = p * qe = 65537d = pow(e, -1, (p - 1) * (q - 1))public_key = (n, e)private_key = (n, d)
def encap(pk, rng): mod, exp = pk K = rng.randint(1, mod - 1) c = pow(K, exp, mod) return (c, K)
def decap(sk, c): mod, dec_exp = sk return pow(c, dec_exp, mod)
rng = random.Random(7) # fixed seed for reproducibilityc, K_alice = encap(public_key, rng)K_bob = decap(private_key, c)print(K_alice == K_bob)print(0 < K_bob < n)# ==> True# ==> TrueThe round trip shows the toy is correct: decapsulation recovers the key that encapsulation chose. It is not secure, and breaking it takes only the public key. The question a KEM must answer well is a real-or-random one: shown a ciphertext and a key , can an outsider tell whether is the key inside or an unrelated random key? Against the toy it can. It computes and compares the result with . The two match when is the encapsulated key, and an unrelated random key matches with probability about . Correctness constrains what the honest parties compute and says nothing about what an attacker can learn.
The toy also gives its key away to one decapsulation query. Textbook RSA is malleable: decapsulating the different ciphertext returns , and multiplying by recovers . Exercise 7 works that attack, including the care it needs in choosing , but the comparison above needs no query at all.
ElGamal encryption is Diffie-Hellman used to send a message. Bob’s public key is a Diffie-Hellman share . To encrypt a group element , Alice makes a fresh share and multiplies by the shared value , which Bob can recompute as and divide out. The toy below works in the subgroup of squares modulo , whose order is prime. Its messages must be squares, so qualifies. A group this small has no security, since a laptop takes discrete logarithms in it instantly. It shows the mechanism only.
# Toy ElGamal: Diffie-Hellman used to encrypt.# p = 2063 = 2 * 1031 + 1, and g = 4 generates the subgroup of squares,# which has prime order q = 1031. Messages are squares mod p, so 9 qualifies.import random
p, q, g = 2063, 1031, 4rng = random.Random(5) # fixed seed for a reproducible page; keys come from secrets
x = rng.randrange(1, q) # Bob's private keyh = pow(g, x, p) # Bob's public key, a Diffie-Hellman share
def enc(h, m, r): # Alice's fresh share g^r, and m masked by the shared value h^r. return (pow(g, r, p), m * pow(h, r, p) % p)
def dec(x, c): c1, c2 = c shared = pow(c1, x, p) # Bob recomputes h^r as c1^x return c2 * pow(shared, -1, p) % p
m = 9c_first = enc(h, m, rng.randrange(1, q))c_second = enc(h, m, rng.randrange(1, q))print(c_first, c_second, dec(x, c_first) == dec(x, c_second) == m)
# The attacker holds a ciphertext c it cannot read, and can get any OTHER# ciphertext decrypted. It multiplies the hidden message by 4 and asks.c = enc(h, m, rng.randrange(1, q))c_mauled = (c[0], c[1] * 4 % p)reply = dec(x, c_mauled) # one decryption of a different ciphertextprint(c_mauled != c, reply * pow(4, -1, p) % p == m)# ==> (1664, 1910) (1803, 412) True# ==> True TrueThe first line shows what textbook RSA lacks. The same message encrypts to two different ciphertexts because each encryption draws a fresh , so the test that breaks textbook RSA, encrypting a guess and comparing, fails here. Defeating one test does not make a scheme secure. Against a classical eavesdropper, ElGamal’s security rests on an assumption. In a group where the decisional Diffie-Hellman (DDH) assumption holds, an efficient eavesdropper who picks two messages and sees the encryption of one cannot tell which it was noticeably better than by guessing (Exercise 11.5(a) in Boneh & Shoup, 2023). Shor’s algorithm breaks the scheme on a quantum computer by computing discrete logarithms (Chapter 4), and nothing in this chapter changes that.
The second line shows what ElGamal still lacks. The attacker holds a ciphertext it cannot read. It is not allowed to have itself decrypted, but is a different ciphertext, and its decryption is . Dividing by 4 recovers .
The attack needed three things: Bob’s public key, one ciphertext it wanted to read, and one decryption of a different ciphertext. It never touched the private key or a discrete logarithm. Real receivers have given away less than a decryption and still been broken. In 1998 Bleichenbacher showed that an RSA-encrypted SSL 3.0 key-exchange message can be decrypted by anyone who can learn, for ciphertexts of their choosing, one bit: whether the receiver finds the padding valid (Bleichenbacher, 1998). His experiments, run against an oracle he implemented, needed between 300,000 and 2 million queries. Of three SSL servers checked at the time, two gave the bit away through their error alerts. The next section writes these capabilities down as games.
Security as a game
Section titled “Security as a game”Each game is played between a challenger, who generates the keypair and flips a uniform bit , and an attacker, who ends by outputting a guess . The two attack models name the games: indistinguishability under chosen-plaintext attack (IND-CPA) and under adaptive chosen-ciphertext attack (IND-CCA2), where adaptive means the attacker’s queries may continue after it has seen the challenge.
| Game | What the attacker gets | What it must do |
|---|---|---|
| IND-CPA, PKE | The public key, and for two equal-length messages it chose | Guess |
| IND-CCA2, PKE | The same, plus the decryption of any ciphertext other than , before and after the challenge | Guess |
| IND-CPA, KEM | The public key, , and a key that is the key encapsulated in () or a fresh uniform key () | Guess |
| IND-CCA2, KEM | The same, plus the decapsulation of any ciphertext other than | Guess |
The eavesdropper’s game is called a chosen-plaintext attack because the public key already lets the attacker encrypt anything it chooses, so it needs no oracle. The decryption oracle stands for a receiver that can be made to process ciphertexts the attacker supplies: “an active adversary may be able to convince an honest user to decapsulate some ciphertexts of the adversary’s choosing” (National Institute of Standards and Technology, 2025b, sec. 2.3). The oracle returns the whole decryption, so a scheme that survives it also survives any answer a receiver computes from the decryption’s output and public information, such as an error message reporting . Leaks of secret intermediate values, through timing for example, are outside the game, and the FIPS 203 paragraph below returns to them.
The only query the game excludes is , because answering it would let the attacker win against every scheme by asking for and comparing (National Institute of Standards and Technology, 2025b, sec. 2.3). Every other ciphertext may be submitted, including one derived from , which is exactly what the ElGamal attack submitted. The scheme’s answer to such a query may be a rejection. Real receivers keep no list of forbidden ciphertexts: the exclusion only stops the game from counting a trivial win.
The two KEM games choose no messages. The attacker gets together with a key that is either the one inside or a fresh random one, and must say which. The attack on a KEM is an attack on a key.
In the IND-CCA2 game for PKE the attacker’s advantage is
A public-key encryption scheme is IND-CCA2-secure if every polynomial-time adversary’s advantage is negligible in the security parameter (Boneh & Shoup, 2023). The other three rows define their advantages and their notions the same way. The prerequisites page says what makes a function of the security parameter negligible.
Replayed as games, the attacks above are wins for the attacker. Against textbook RSA it wins the IND-CPA game for PKE without a query: encrypt and compare with . A win that needs no query is also an IND-CCA2 win, since that game only adds an oracle the attacker need not use. Against the toy RSA-KEM it wins the KEM game without a query: compute and compare with . Toy ElGamal is a PKE, the encrypt-and-compare test fails against it, and one decryption query wins the IND-CCA2 game. A scheme that withstands the first attack model and fails the second is what cryptographers mean when they call IND-CPA security weaker than IND-CCA2 security: weaker in what the attacker is allowed to do. Key length does not enter into it, and the ElGamal attack works at any size.
The Fujisaki-Okamoto transform: from PKE component to CCA-secure KEM
Section titled “The Fujisaki-Okamoto transform: from PKE component to CCA-secure KEM”Bob cannot catch by inspecting its plaintext. Every group element is a valid ElGamal message, so looks as legitimate as . He could catch it by checking whether the ciphertext is the one an honest sender would have produced for that plaintext, but he cannot recompute that ciphertext, because the sender’s never reaches him. The Fujisaki-Okamoto transform removes the obstacle by deriving the randomness from the message, for a hash function . A ciphertext is then a fixed function of its plaintext and the public key, so Bob can decrypt, re-encrypt, and compare.
The transform also hashes the message into the shared key, so a related message yields an unrelated key. Deriving from makes encryption deterministic, which would let an attacker test guesses at a chosen message. In a KEM the message is a fresh random value from a set too large to search at real parameters, so guessing it is infeasible, which is one reason the modern transforms output a KEM.
The toy below applies the pattern to the ElGamal above. Encapsulation draws a random group element , encrypts it with randomness , and outputs the key . Decapsulation decrypts, re-encrypts with , and compares. On a mismatch it does not report failure. It returns , computed from a secret group element that Bob draws like a message and stores beside his private key, so the caller receives a key-shaped value either way.
# The same toy ElGamal, put through the Fujisaki-Okamoto pattern.import hashlibimport random
p, q, g = 2063, 1031, 4rng = random.Random(5) # fixed seed for a reproducible page; keys come from secrets
x = rng.randrange(1, q) # Bob's private keyh = pow(g, x, p) # Bob's public keyz = pow(g, rng.randrange(q), p) # Bob's rejection seed, a secret group element
def enc(h, m, r): return (pow(g, r, p), m * pow(h, r, p) % p)
def dec(x, c): c1, c2 = c return c2 * pow(pow(c1, x, p), -1, p) % p
def hash_to_int(tag, *parts): data = tag.encode() + b"|" + b"|".join(str(v).encode() for v in parts) return int.from_bytes(hashlib.sha256(data).digest(), "big")
def G(m): # coins derived from the message return hash_to_int("G", m) % q
def H(*parts): # the shared key, truncated to 32 bits for the page return hash_to_int("H", *parts) % 2**32
def encap(h): m = pow(g, rng.randrange(q), p) # a random message, never sent in clear c = enc(h, m, G(m)) # coins come from G(m), not from rng return c, H(m, c)
# No input checks: decap expects two subgroup elements, and c1 = 0 raises.def decap(x, z, h, c): m_prime = dec(x, c) if enc(h, m_prime, G(m_prime)) == c: # re-encrypt and compare return H(m_prime, c) return H(z, c) # implicit rejection
c, K = encap(h)print(decap(x, z, h, c) == K)
# The mauling from before. Decryption gives 4m, but the coins G(4m) do not# reproduce the first component, so the comparison fails.c_mauled = (c[0], c[1] * 4 % p)m_prime = dec(x, c_mauled)print(enc(h, m_prime, G(m_prime)) == c_mauled)
# What the attacker receives: a value fixed by (z, c_mauled), not by K.k1 = decap(x, z, h, c_mauled)k2 = decap(x, z, h, c_mauled)k3 = decap(x, z, h, (c[0], c[1] * 9 % p))print(k1 == k2, k1 != K, k1 != k3)# ==> True# ==> False# ==> True True TrueThe first line is the honest round trip. The second is the check rejecting the mauled ciphertext: it decrypts to , and re-encrypting with randomness gives a different first component, because and differ. The check tests consistency, not whether the sender knew . A ciphertext passes exactly when it is the encryption of its own decryption under the derived randomness, so a mauled ciphertext whose two exponents happened to agree would pass. The theorem below bounds the attacker’s advantage over everything it might submit, lucky queries included.
The third line is what the attacker receives instead. The value is the same on every query with that ciphertext, because it is computed from and the ciphertext and from nothing random. It is unrelated to the challenge key. Because the ciphertext is an input, different rejected ciphertexts get fallbacks that behave like independent random keys rather than one shared constant, and like independent keys they can collide. One fixed fallback would be recognizable once seen.
The toy shows the mechanism and proves nothing about security. Its message set has 1031 elements, so an attacker can guess outright, which is the term in the theorem below. If were an ideal random function, a given mauled ciphertext would pass the check with probability , the chance that equals . The 1031 there counts the values can output, not the messages, although in ElGamal the two numbers are equal. ML-KEM’s messages are 256-bit strings.
A ciphertext that passes is an honest encryption of some message under Bob’s public key. Anyone holding that key can make one by running encapsulation, so passing says nothing about who sent it. The transform does not need every ciphertext an attacker makes to fail. It needs every accepted ciphertext to yield a key derived from that ciphertext’s own plaintext, which tells the attacker nothing about the challenge key. Authenticating the sender is a different job, and Chapter 6 gives it to signatures.
Hofheinz, Hövelmanns and Kiltz (HHK) analyze this pattern as one of a family of variants (Hofheinz et al., 2017, sec. 3). In their notation the toy’s pattern is , with implicit rejection, and the explicit-rejection variant returns a distinguished symbol on a failed check instead.
The chapter states the theorem for the implicit-rejection variant, since that is the form ML-KEM relies on, and does not prove it.
Theorem (, informal). Let be a public-key encryption scheme with message space . Assume is IND-CPA-secure and -correct, where bounds the expectation over key generation of the worst-case message decryption-failure probability, so a scheme cannot be called -correct on the strength of an average over . Let and be two hash functions modeled as random oracles. Define a KEM as follows. Key generation runs and samples a uniform private seed , drawn from the same message space the construction encapsulates over. Encapsulation samples a random , computes (the encryption coins are derived from ), and returns with . Decapsulation decrypts to and returns the implicit-rejection fallback if output or if the re-encryption differs from ; otherwise it returns . Both failure conditions route to the fallback, and omitting the case leaves the transform unsound. Then is IND-CCA2-secure in the random-oracle model, with
where counts the adversary’s random-oracle queries (Hofheinz et al., 2017).
Two things about that bound are worth reading off. The term is why the theorem needs a large message space: an adversary that can guess wins outright, so must be big enough to make that term negligible. And the term is why -correctness has to hold in the worst case over messages rather than on average: a chosen-ciphertext adversary picks which messages to push through the failure branch.
Returning instead of an error is implicit rejection. It is not what makes the transform secure. The re-encryption check and the hashing do that, and the explicit-rejection variant, which returns a distinguished symbol , also has a proof in the random-oracle model under an extra condition on the encryption scheme (Hofheinz et al., 2017). What implicit rejection changes is the interface. A caller receives a key-shaped value either way, so a protocol built on the KEM has no rejection signal to branch on or to leak, and that signal is what Bleichenbacher’s attack read.
ML-KEM applies this pattern to an encryption scheme that FIPS 203 calls K-PKE, built on Module-LWE: Chapter 9 develops the problem and Chapter 11 builds the scheme. Three details differ from the toy. First, the success-path key and the encryption randomness come from one hash, , so the key depends on the message and the encapsulation key but not on the ciphertext.
Second, a failed check returns , 32 bytes computed from a secret seed stored in the decapsulation key and from the ciphertext. The value is the same every time for a given key and ciphertext, and the caller receives 32 bytes either way, with no flag. FIPS 203 calls the comparison result the “implicit reject” flag, treats it as secret intermediate data, and states that “returning the value of the flag as an output in any form is not permitted” (National Institute of Standards and Technology, 2024, sec. 6.3). Implementations still have to compute the check and the conditional assignment without leaking the secret reject flag through timing, cache, or other side channels. NIST addresses that in SP 800-227 rather than in FIPS 203: cryptographic modules for KEMs “should be designed with appropriate countermeasures against side-channel attacks”, constant-time implementations among them (National Institute of Standards and Technology, 2025b, sec. 3.3).
Third, input checking is a separate step that runs before any of this. FIPS 203 §7.3 lists three checks: the ciphertext has the parameter set’s length, the decapsulation key has its length, and the encapsulation key embedded in the decapsulation key hashes to the value stored beside it. ML-KEM.Decaps “shall not be run” on inputs that have not been checked. FIPS 203 does not say how a failed check is reported, so what the caller sees then is the implementation’s choice. The ciphertext check runs on every call. The key checks need not, because FIPS 203 lets assurance that they were done come from other means (National Institute of Standards and Technology, 2024, sec. 7.3). Input checking passes any ciphertext of the right length, whoever made it. Once the checks have passed, a ciphertext that fails re-encryption produces the implicit-rejection key.
Two more failures and their fixes
Section titled “Two more failures and their fixes”Textbook Diffie-Hellman falls to a man-in-the-middle without authentication. Against a purely passive eavesdropper it holds up: seeing and , that adversary cannot compute under the computational Diffie-Hellman (CDH) assumption. Concluding that the derived key also looks random to it is a stronger statement needing a stronger assumption. That assumption is hash Diffie-Hellman, which CDH implies when the key-derivation function is modeled as a random oracle. DDH plus a suitable extractor is the other standard route (Boneh & Shoup, 2023).
An active adversary defeats the exchange without touching either assumption. Eve intercepts both messages, forwarding to Bob in place of Alice’s , and to Alice in place of Bob’s . Alice, who received , computes , which Eve recomputes as . Bob, who received , computes , which Eve recomputes as . Each honest party pairs its own secret exponent with Eve’s matching public value, never with the other party’s, so Eve ends up holding two unrelated shared secrets while Alice and Bob each believe they share one with the other.
The defense is to authenticate the protocol, either via a signature on the exchange (as in TLS 1.3) or via a pre-shared long-term public key. Chapter 28 walks the TLS 1.3 handshake and where post-quantum key establishment lands in it; Chapter 29 covers the certificate chain that carries the authenticating signature, for a hierarchy the team owns.
Textbook RSA encryption has a fix of its own. Optimal asymmetric encryption padding (OAEP) randomizes the input before the RSA function sees it (Bellare & Rogaway, 1994), so the decryption of a mauled ciphertext is a random-looking block that almost never parses as a valid OAEP encoding. Real RSA-KEM is a different construction, and it is not OAEP. It encrypts a random integer with raw RSA, much as this chapter’s toy does, and derives the shared secret as rather than using as the key, so no padding is involved and every is admissible (Housley & Turner, 2025). Testing a candidate key then needs , and recovering from the ciphertext means inverting RSA, so the comparison that broke the toy no longer works. RSAES-OAEP is a separate padded construction for encrypting a chosen message (Moriarty et al., 2016). The two are often conflated and should not be.
Tradeoffs across post-quantum deployments
Section titled “Tradeoffs across post-quantum deployments”The three primitives are not interchangeable at deployment time.
| Primitive | What the receiver ends up with | Classical example | Post-quantum status |
|---|---|---|---|
| Key agreement | A shared secret both parties contributed randomness to | Diffie-Hellman | The Ring-LWE variants in Chapter 9 are not widely deployed as standalone interactive protocols |
| Public-key encryption | The specific message the sender chose | RSA-OAEP | Post-quantum standards expose KEM APIs rather than direct PKE APIs |
| KEM | A random symmetric key that encapsulation generates | The toy RSA-KEM above | ML-KEM (Chapter 11) is the finalized standard; HQC (Chapter 21) is selected but not yet published |
Two rows of that last column need qualifying. Schemes such as ML-KEM and HQC do contain internal PKE-like components: K-PKE for ML-KEM, and the quasi-cyclic code-based PKE inside HQC. Those components are wrapped into KEMs before they are exposed to protocols, so a deployment never programs against the PKE API. ML-KEM’s standard is FIPS 203, published and final (National Institute of Standards and Technology, 2024). HQC was selected in March 2025 as a backup KEM for future standardization (National Institute of Standards and Technology, 2025a). No draft of its standard had been published by 19 October 2026, so it should not be described as a finalized FIPS standard.
IETF working groups are defining ML-KEM key establishment for TLS 1.3, SSH, and the Internet Key Exchange Protocol Version 2 (IKEv2) that sets up IPsec, and the three documents differ in both status and shape. The TLS one finished first: RFC 10024, Standards Track, specifies hybrid NamedGroups. The SSH one followed on 31 August 2026: RFC 10042, Informational, specifies hybrid key-exchange methods instead (Kampanakis et al., 2026; Kwiatkowski et al., 2026). The IKEv2 standards-track draft is the permissive one: it assigns the ML-KEM identifiers and allows ML-KEM alone as well as alongside another exchange, carried by the generic multiple-key-exchange framework of RFC 9370 (Kampanakis, 2026; Tjhai et al., 2023).
Deployment ran ahead of publication rather than following it: Go 1.24 enabled X25519MLKEM768 by default in crypto/tls while the TLS document was still a draft (The Go Authors, 2025; Valsorda, 2024). Chapters 27 and 28 walk the TLS 1.3 codepoints and rollout state in detail; neither covers SSH or IKEv2, whose documents are cited here and not developed further in this edition.
The post-quantum standardization effort concentrated on KEMs, not on PKE or interactive key agreement. The reason is that every practical protocol that used to need a key-agreement or PKE primitive (TLS, SSH, IPsec, hybrid public-key encryption stacks) can be rewired to use a KEM with a symmetric cipher on top. KEMs also fit hybrid encryption and one-pass key transport cleanly once the recipient’s encapsulation key is available. In interactive protocols such as TLS, SSH, or IKEv2 they are composed into the existing handshake rather than eliminating interaction altogether. “KEMs are cheaper” is therefore a context-dependent claim, not a universal one: ML-KEM ciphertexts are larger than X25519 key shares, for example. ML-KEM is the first post-quantum KEM standardized by NIST. Its security depends on the Module-LWE assumption Chapter 9 develops and on the Fujisaki-Okamoto transform above (Hofheinz et al., 2017; National Institute of Standards and Technology, 2024).
Where Chapter 5 ends and Chapter 6 picks up
Section titled “Where Chapter 5 ends and Chapter 6 picks up”This chapter separated the three confidentiality primitives and put a security definition behind each one. Public-key encryption delivers the message the sender chose; key agreement delivers a secret both sides contributed randomness to; a KEM delivers a random key that encapsulation generates rather than one the sender supplies. IND-CCA2 is the bar for the first and the third, and the chapter showed two ways of missing it. Textbook RSA falls to one multiplication by , and a KEM built by naively wrapping a merely IND-CPA-secure PKE should not be assumed to clear the bar either. The Fujisaki-Okamoto transform is what closes that gap, and Chapter 11 instantiates it on the Module-LWE-based K-PKE to obtain ML-KEM.
Textbook Diffie-Hellman fails for a different reason, and that reason is the seam into Chapter 6. What breaks it is not the ciphertext-mauling attack that broke the toy KEM; it is the absence of authentication. The man-in-the-middle above breaks no computational assumption, it substitutes Eve’s own public values for Alice’s and Bob’s. Authentication is the signature’s job, and signatures carry their own security definition and their own textbook failures. Chapter 6 states that definition as the EUF-CMA game and forges a textbook RSA signature using the same multiplicative structure this chapter used to maul a ciphertext. It also pays off Chapter 4’s Exercise 4, recovering an ECDSA private key from two signatures that reused a nonce.
The two games share a skeleton. In both, the challenger holds the private key, and the attacker gets the public key and an oracle that runs the private-key operation on inputs of its choice. Both rule out a trivial win, in different ways: this chapter’s game excludes one decapsulation query, , while Chapter 6’s answers every signing query and counts a forgery only if its message was never submitted for signing. What changes is the goal. This chapter’s attacker tried to learn something hidden, while Chapter 6’s tries to produce something new: a valid signature on a message the signer was never asked to sign.
Exercises
Section titled “Exercises”-
Correctness is not security. The toy RSA-KEM decapsulates every ciphertext correctly. In the KEM game you receive the public key , a ciphertext and a key that is either the encapsulated key or a fresh random one. Win without asking for any decapsulation. Then say what this shows about correctness and security, and name the change real RSA-KEM makes that stops your attack.
-
What the oracle stands for. In the IND-CCA2 game the attacker may decapsulate any ciphertext except . (a) Name two behaviors of a real receiver that this oracle stands for. (b) Why must the game exclude itself? (c) Why is it right that a ciphertext derived from is still a fair question?
-
What “weaker” means. An attacker wins the IND-CPA game against textbook RSA encryption. The same attack fails against toy ElGamal, and one decryption query wins the IND-CCA2 game against it. (a) Give the IND-CPA attack on textbook RSA and say why it fails against ElGamal. (b) Give the IND-CCA2 attack on ElGamal. (c) State in what sense IND-CPA is weaker than IND-CCA2, and why a longer key would not turn an IND-CPA scheme into an IND-CCA2 one.
-
Reproducible randomness and the fallback. Change the toy in the Fujisaki-Okamoto section so that a failed check returns
rng.getrandbits(32)instead ofH(z, c). Before running it, predict its three printed lines. Then answer two questions. (a) Why must the encryption randomness be derived from the message for the re-encryption check to mean anything? (b) Why is the fallback computed from a secret and from , rather than being a fixed dummy key or fresh random bytes on each call? -
The standard’s interface, and two claims to challenge. FIPS 203 requires an ML-KEM ciphertext to be bytes long (National Institute of Standards and Technology, 2024, sec. 7.3), and ML-KEM-768 has , and (Table 2 in National Institute of Standards and Technology, 2024). An ML-KEM-768 decapsulation receives two inputs: a ciphertext of 1,087 bytes, and a 1,088-byte string the attacker generated at random. (a) What does FIPS 203 require in each case, and what does the caller see? (b) A colleague says: “Decapsulation returned a key, so the ciphertext came from our partner, and ML-KEM is CCA-secure because of implicit rejection.” Correct both halves. (c) Name one way a deployment differs from the model in which the chapter’s theorem holds.
-
Run the toy DH on a larger prime. Replace with , which is prime, and keep . Pick your own secret exponents and , run the same
pow-based exchange, and confirm thatalice_shared == bob_shared. Separately computepow(5, 1031, 2063)and report whether the result is . How large does need to be before discrete logarithm becomes hard on a classical computer? Answer it from the Logjam analysis (Adrian et al., 2015) and NIST’s key-management guidance (Barker, 2020). -
Round-trip and maul the toy RSA-KEM. Take the toy RSA-KEM snippet above, run
encapanddecap, and confirm the round-trip. Then try a mauling attack. Take , compute , and rundecap(private_key, c_prime). Check that the result is , then recover as .Now show why “any invertible ” is not good enough. In the CCA game the challenge ciphertext is excluded from the oracle, so the attack needs , and invertibility does not deliver that. Take and pick by the Chinese remainder theorem with and . Verify that this is invertible and is not , then verify that exactly, so the query is forbidden. Then show that escapes this for every , and under every valid two-prime RSA key rather than only this one. The property your argument needs is what being invertible modulo forces about and .
Finally, two short answers. Say what extra restriction the same attack needs in the PKE game, where the adversary picks the challenge plaintexts instead of encapsulation sampling . Then explain in two sentences why the attack fails against an -transformed KEM (the implicit-rejection transform this chapter teaches, which ML-KEM uses in a modified form).
-
Prove the toy KEM’s correctness, and bound the non-coprime fraction. First, show that
decap(private_key, c) == Kfor everyKproduced byencap. The clean argument is by the Chinese remainder theorem. Fermat’s little theorem gives and , trivially so when or , and CRT lifts the pair to . Euler’s theorem from Chapter 4 covers only the coprime case; CRT closes the remaining residues.Second, bound the non-coprime fraction. Use inclusion-exclusion on the sets of -multiples and -multiples in . Show that a uniform shares a factor with with probability , which for the 32-bit primes in the snippet is below . Then say what this fraction does not govern. It is not a correctness bound, since the first part showed
decapis exact on the whole range. Nor is it the mauling attack’s failure rate: that attack needs its blinding factor to be invertible, not , and it chooses deliberately. Which would you pick, for an RSA modulus, to make the choice trivially safe? -
State the theorem in your own words. Without copying from this chapter, state the inputs, the output, the role of the private seed , the behavior of decapsulation when the re-encryption check fails, the security game, and the assumption on the random oracles. The exercise is not to prove the theorem; it is to produce a statement precise enough that someone else could look up the proof in (Hofheinz et al., 2017).
Worked solutions and editorial notes for these exercises are in Appendix D, Chapter 5. A separate track, for rebuilding rather than reading: the package exercises/ch05-kem-primitives has every function the chapter teaches replaced by a stub. Run PQC_IMPL=exercises pytest tests/ch05 to grade your version against the suite that proves the reference one.
References
Section titled “References”Last updated: