Skip to content

Appendix D: Solutions for Chapter 5

This page collects solutions and editorial notes for the exercises in Chapter 5: KEMs vs key agreement vs public-key encryption. Compute and derivation exercises have worked solutions; open-ended exercises have an editorial note describing what a strong answer addresses.

Every block below is self-contained and runs on the standard library alone. The fuller versions of these routines, with the mauling attack and the multiplicative-order computation written out, are in the kem_primitives package under solutions/ch05-kem-primitives. From a clone of the companion repository, pytest tests/ch05 runs its suite. Appendix C has the setup.

The exchange round-trips because gabgba(modp)g^{ab} \equiv g^{ba} \pmod p regardless of how aa and bb are sampled. The auxiliary computation 51031mod20635^{1031} \bmod 2063 returns 2062, which is 1mod2063-1 \bmod 2063. The order of 5 in F2063\mathbb{F}_{2063}^* must divide p1=2062=21031p - 1 = 2062 = 2 \cdot 1031, so it is 1, 2, 1031, or 2062. The first three are ruled out: 515 \neq 1, 52=2515^2 = 25 \neq 1, and 51031115^{1031} \equiv -1 \neq 1. So the order is 2062 and g=5g = 5 is a primitive root, generating the full multiplicative group rather than a strict subgroup. Real DH groups still prefer prime-order subgroups of carefully chosen safe primes for unrelated reasons (subgroup-confinement attacks).

Classical hardness of the discrete log on Fp\mathbb{F}_p^* is set against the general number field sieve, which runs in sub-exponential time. Modern parameter sets use p2048|p| \ge 2048 bits. The Logjam analysis put the 1024-bit primes that protected legacy TLS at the edge of nation-state-feasible per-prime precomputation (Adrian et al., 2015), so 2048 bits is the conservative floor used today.

p, g = 2063, 5
print(pow(5, 1031, p))
# ==> 2062

The mauling works because RSA encryption is multiplicative: Enc(r)Enc(K)=Enc(rK)modn\mathrm{Enc}(r) \cdot \mathrm{Enc}(K) = \mathrm{Enc}(rK) \bmod n, so the receiver decrypts to rKrK instead of KK. With r=2r = 2 the attacker recovers KK exactly. Since rr was chosen invertible, multiplying the oracle’s answer by r1modnr^{-1} \bmod n returns KK itself, which is the recovery step the exercise asks for and the one test_mauling_recovers_the_encapsulated_key pins. One decapsulation query on a ciphertext other than the challenge therefore hands over the challenge key, which is exactly the IND-CCA capability the FO transform must defend against.

The FO⊥̸\mathrm{FO}^{\not\perp} transform recomputes the ciphertext during decap by re-encrypting the recovered plaintext under encryption coins derived from a random oracle of that plaintext, then compares the re-encryption byte-for-byte to the received ciphertext. Over a randomized public-key encryption scheme each message has exactly one ciphertext under its derived coins. So a cc' obtained by applying an algebraic relation to the challenge is essentially never the re-encryption of whatever it decrypts to, and the check fails. Decap then returns the implicit-rejection fallback Kˉ=H(z,c)\bar{K} = H(z, c'), which depends on the private seed zz and on the ciphertext the attacker submitted, not on the challenge key.

Why “any invertible r1r \neq 1” is not good enough. The CCA oracle refuses the challenge ciphertext, so the attack needs ccc' \neq c as well as an invertible rr, and invertibility does not imply it. Take K=pK = p, so c0(modp)c \equiv 0 \pmod p, and choose rr by the Chinese remainder theorem with r2(modp)r \equiv 2 \pmod p and r1(modq)r \equiv 1 \pmod q. That rr is coprime to nn (it is 22 mod pp and 11 mod qq, so neither prime divides it) and is not 1modn1 \bmod n. Yet c=cc' = c: modulo pp both sides are 00, and modulo qq multiplication by re1r^e \equiv 1 changes nothing. For the chapter’s modulus that rr is 6,957,319,475,842,508,6466{,}957{,}319{,}475{,}842{,}508{,}646, and the query it produces is forbidden.

r=2r = 2 escapes this for every K[1,n1]K \in [1, n-1], and not merely on this modulus: it holds for every valid two-prime RSA key. The collision c=cc' = c means c(2e1)0(modn)c\,(2^e - 1) \equiv 0 \pmod n. First note c≢0(modn)c \not\equiv 0 \pmod n, since nKen \mid K^e would force pKp \mid K and qKq \mid K, hence nKn \mid K, which K[1,n1]K \in [1, n-1] excludes. So some prime {p,q}\ell \in \{p, q\} does not divide cc, and the collision would require 2e1(mod)2^e \equiv 1 \pmod \ell.

That is impossible, which is the property of ee the exercise asks for. Because dd exists, ee is invertible modulo φ(n)=(p1)(q1)\varphi(n) = (p-1)(q-1), so gcd(e,p1)=gcd(e,q1)=1\gcd(e, p-1) = \gcd(e, q-1) = 1. If 2e1(mod)2^e \equiv 1 \pmod \ell then ord(2)\mathrm{ord}_\ell(2) divides ee, and it divides 1\ell - 1 by Fermat, so it divides gcd(e,1)=1\gcd(e, \ell - 1) = 1. Then 21(mod)2 \equiv 1 \pmod \ell, forcing 1\ell \mid 1, which no prime does. So r=2r = 2 is collision-free under any valid two-prime RSA key, and no per-modulus check is needed.

In the PKE game rather than the KEM game one further restriction is needed, because there the adversary chooses the challenge messages. If it chose mb=0m_b = 0 then c=0c^* = 0 and c=cc' = c^* for every rr, so the attack must pick two distinct non-zero challenge representatives, preferably coprime to nn, before taking r=2r = 2. Encapsulation cannot hand the KEM adversary that problem, since it samples KK from [1,n1][1, n-1].

The transform is not claiming that every ciphertext other than the challenge fails the check, and a strong answer avoids saying so. An attacker can always run encapsulation honestly and submit a well-formed cc', which passes by construction and yields its own key H(m,c)H(m', c'). What the transform buys is that an accepted ciphertext produces a key derived from its own plaintext, so nothing the oracle returns is algebraically tied to the challenge key.

The toy RSA-KEM is a weaker starting point than that, and the difference is worth stating. Textbook RSA has no encryption coins to derandomize, so the mauled c=crec' = c \cdot r^e is a legitimate encryption of rKrK and would survive a re-encryption check. What defeats the attack in an FO-style KEM is that the shared secret is a hash of the recovered message rather than the message itself: the attacker obtains a hash of rKrK, which reveals nothing about the hash of KK.

Correctness, for every KK and not only the coprime ones. Encap\mathrm{Encap} samples KK uniformly from {1,,n1}\{1, \ldots, n-1\} with no coprimality test, and outputs c=Kemodnc = K^e \bmod n. Decap\mathrm{Decap} computes cdmodnc^d \bmod n. The RSA setup chooses e,de, d with ed1(modφ(n))ed \equiv 1 \pmod{\varphi(n)}, so ed=1+k(p1)(q1)ed = 1 + k(p-1)(q-1) for some non-negative integer kk.

Work modulo each prime separately. If pKp \nmid K, Fermat’s little theorem gives Kp11(modp)K^{p-1} \equiv 1 \pmod p, so

Ked=K(Kp1)k(q1)K(modp).K^{ed} = K \cdot \bigl(K^{p-1}\bigr)^{k(q-1)} \equiv K \pmod p.

If pKp \mid K, then both KedK^{ed} and KK are 0(modp)\equiv 0 \pmod p, so the congruence holds trivially. Either way KedK(modp)K^{ed} \equiv K \pmod p, and the identical argument gives KedK(modq)K^{ed} \equiv K \pmod q. Since pp and qq are distinct primes, the Chinese remainder theorem lifts the two congruences to KedK(modpq)K^{ed} \equiv K \pmod{pq}. Both KK and the value Decap\mathrm{Decap} returns lie in [0,n1][0, n-1], so they are equal rather than merely congruent, and Decap(sk,c)=K\mathrm{Decap}(\mathrm{sk}, c) = K for every KK that Encap\mathrm{Encap} can produce.

Euler’s theorem from Chapter 4 proves only the case gcd(K,n)=1\gcd(K, n) = 1, because Kφ(n)1(modn)K^{\varphi(n)} \equiv 1 \pmod n needs KK invertible. The per-prime argument above is what closes the remaining residues, and it is why the chapter can call the toy KEM’s correctness exact rather than probabilistic.

The non-coprime fraction. KK fails to be coprime to n=pqn = pq exactly when pKp \mid K or qKq \mid K. Let A={K[1,n1]:pK}A = \{K \in [1, n-1] : p \mid K\} and B={K[1,n1]:qK}B = \{K \in [1, n-1] : q \mid K\}. Then A=q1|A| = q - 1 (the multiples of pp in [1,n1][1, n-1] are p,2p,,(q1)pp, 2p, \ldots, (q-1)p), and similarly B=p1|B| = p - 1. The intersection ABA \cap B is the set of multiples of pq=npq = n in [1,n1][1, n-1], which is empty. By inclusion-exclusion,

AB=(q1)+(p1)0=p+q2.|A \cup B| = (q - 1) + (p - 1) - 0 = p + q - 2.

The exact probability is therefore p+q2n1\frac{p + q - 2}{n - 1}, and it is strictly below 1p+1q=p+qn\frac{1}{p} + \frac{1}{q} = \frac{p+q}{n}. Cross-multiplying reduces that inequality to p+q<2np + q < 2n, which holds for any primes p,q2p, q \ge 2 since 2pqpq=p(q1)+q(p1)p+q2pq - p - q = p(q-1) + q(p-1) \ge p + q.

For the chapter snippet’s two 32-bit primes the exact fraction is about 6.27×10106.27 \times 10^{-10}, below 2309.31×10102^{-30} \approx 9.31 \times 10^{-10}, which is the bound the chapter quotes.

What this fraction does not govern. Not Decap\mathrm{Decap} correctness, which the first half showed is exact on the whole range. And not the mauling attack of Exercise 2 either, which is the tempting misreading. That attack needs its blinding factor rr to be invertible, not the encapsulated KK, and rr is chosen by the attacker rather than sampled: for an RSA modulus, which is odd, r=2r = 2 is invertible with no test needed. Had rr been drawn uniformly the same count would give its rejection rate, which is the only sense in which the two quantities are related.

Real RSA-KEM samples a random integer much as this toy does. The difference is what it does with it, and it is not OAEP. RSA-KEM encrypts a random zz with raw RSA and derives the shared secret as KDF(z)\mathrm{KDF}(z) rather than using zz as the key, so no padding is involved and every zz is admissible (Housley & Turner, 2025). 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.

def coprime_failure_bound(p, q):
n = p * q
return (p + q - 2) / (n - 1)
for p, q in [(53, 61), (1009, 1013), (3184935163, 3199286161)]:
print(f"p={p:>14} q={q:>14} fraction={coprime_failure_bound(p, q):.3e}")
# ==> p= 53 q= 61 fraction=3.465e-02
# ==> p= 1009 q= 1013 fraction=1.976e-03
# ==> p= 3184935163 q= 3199286161 fraction=6.265e-10

The third row is the chapter snippet’s own modulus. The function returns the exact fraction, not the 1/p+1/q1/p + 1/q upper bound.

A reference statement to compare against your own version:

  • Inputs. An IND-CPA-secure public-key encryption scheme Π=(Gen,Enc,Dec)\Pi = (\mathrm{Gen}, \mathrm{Enc}, \mathrm{Dec}) that is δ\delta-correct, a key-derivation random oracle HH, and a coin-derivation random oracle GG.
  • Output. A KEM Π=(Gen,Encap,Decap)\Pi' = (\mathrm{Gen}', \mathrm{Encap}', \mathrm{Decap}') that is IND-CCA2-secure in the random oracle model. Gen\mathrm{Gen}' runs Π.Gen\Pi.\mathrm{Gen} and also samples a uniform private seed zMz \gets \mathcal{M}, from the message space itself, which the decapsulation key carries. Naming the domain matters: the bound’s guessing term is written over M|\mathcal{M}|, so an answer that samples zz from an independent λ\lambda-bit space owes a separate qH/2λq_H / 2^{\lambda} term for seed guessing. The query count is part of it, since the adversary gets one attempt per hash query. Encap\mathrm{Encap}' samples a plaintext mm, computes the ciphertext c=Enc(pk,m;G(m))c = \mathrm{Enc}(\mathrm{pk}, m; G(m)) with coins derived from G(m)G(m), and sets the shared key to K=H(m,c)K = H(m, c). Decap\mathrm{Decap}' runs Dec\mathrm{Dec} to get mm', re-encrypts under G(m)G(m'), and returns H(m,c)H(m', c) on a byte-for-byte match. It takes the fallback branch on a mismatch and when Dec\mathrm{Dec} itself returns \perp. A statement that covers only the mismatch is incomplete.
  • Role of the private seed zz. On a failed re-encryption check, Decap\mathrm{Decap}' returns Kˉ=H(z,c)\bar{K} = H(z, c) rather than a distinguished \perp symbol. This is what makes the rejection implicit: the caller cannot tell a rejection from a success by inspecting the output. Because zz is secret and per-key, the adversary cannot compute the fallback itself; because the fallback depends on cc, two different mauled ciphertexts get independently distributed fallback values, colliding only with negligible probability. Both properties are load-bearing, and neither holds for a fixed dummy key.
  • Security game. IND-CCA2 for KEMs: the challenger returns (c,Kb)(c^*, K_b) where K0K_0 is the real encapsulated key and K1K_1 is uniform, and an adversary with a decapsulation oracle on every ciphertext except cc^* cannot guess bb with non-negligible advantage.
  • Assumption. IND-CPA security of Π\Pi, δ\delta-correctness in the worst case over messages, a message space large enough that qRO/Mq_{\mathrm{RO}} / |\mathcal{M}| is negligible, and the random oracle model for GG and HH. The bound is qROδ+3qRO/M+3AdvIND-CPAq_{\mathrm{RO}}\delta + 3 q_{\mathrm{RO}} / |\mathcal{M}| + 3\,\mathrm{Adv}^{\text{IND-CPA}} with qRO=qG+qHq_{\mathrm{RO}} = q_G + q_H (Hofheinz et al., 2017).
  • Tightness. This IND-CPA-based reduction is tight: the advantage term carries only a constant factor of 3, with no factor of qROq_{\mathrm{RO}} multiplying it. Calling the FO transform non-tight refers to the route from a one-way assumption instead, and to the QROM reductions. An answer that labels this reduction non-tight has the tightness picture backwards.

A complete answer also distinguishes the two rejection styles by name, FO\mathrm{FO}^{\perp} versus FO⊥̸\mathrm{FO}^{\not\perp}. It should state the QROM caveat too: the original FO theorem is in the classical ROM, and QROM tightness for ML-KEM’s variant is the subject of subsequent work (Chapter 11 returns to this).

One further point separates the teaching theorem from the deployed scheme. FIPS 203’s ML-KEM does not derive its success-path key as H(m,c)H(m, c): it computes (K,r)=G(mH(ek))(K, r) = G(m \,\|\, H(\mathrm{ek})), omitting the ciphertext, and uses J(zc)J(z \,\|\, c) for the implicit-rejection fallback. An answer that states the abstract theorem correctly and flags that ML-KEM deviates from it is stronger than one that conflates the two.

Adrian, D., Bhargavan, K., Durumeric, Z., Gaudry, P., Green, M., Halderman, J. A., Heninger, N., Springall, D., Thomé, E., Valenta, L., VanderSloot, B., Wustrow, E., Zanella-Béguelin, S., & Zimmermann, P. (2015). Imperfect Forward Secrecy: How Diffie-Hellman Fails in Practice. Proceedings of the 22nd ACM SIGSAC Conference on Computer and Communications Security (CCS 2015), 5–17. https://doi.org/10.1145/2810103.2813707
Hofheinz, D., Hövelmanns, K., & Kiltz, E. (2017). A modular analysis of the Fujisaki-Okamoto transformation. Theory of Cryptography – TCC 2017, Part I, 10677, 341–371. https://doi.org/10.1007/978-3-319-70500-2_12
Housley, R., & Turner, S. (2025). Use of the RSA-KEM Algorithm in the Cryptographic Message Syntax (CMS). IETF RFC 9690. https://doi.org/10.17487/RFC9690
Moriarty, K., Kaliski, B., Jonsson, J., & Rusch, A. (2016). PKCS #1: RSA Cryptography Specifications Version 2.2. RFC 8017. https://doi.org/10.17487/RFC8017