<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Parag Mali - tag: padding-oracle</title><description>Posts tagged padding-oracle.</description><link>https://paragmali.com/</link><language>en-US</language><lastBuildDate>Sun, 19 Jul 2026 05:08:43 GMT</lastBuildDate><atom:link href="https://paragmali.com/tags/padding-oracle/rss.xml" rel="self" type="application/rss+xml"/><item><title>How RSA Breaks in Real Life: ROCA, Bleichenbacher&apos;s Ghosts, FREAK, and the Keys That Shared a Prime</title><link>https://paragmali.com/blog/how-rsa-breaks-in-real-life-roca-bleichenbachers-ghosts-frea/</link><guid isPermaLink="true">https://paragmali.com/blog/how-rsa-breaks-in-real-life-roca-bleichenbachers-ghosts-frea/</guid><description>No one has ever factored a strong, deployed RSA key -- yet ROCA, Bleichenbacher&apos;s oracle, DROWN, and FREAK broke real RSA anyway. The break was never the factoring.</description><pubDate>Fri, 17 Jul 2026 00:00:00 GMT</pubDate><content:encoded>
**No one has ever factored a strong, correctly generated RSA key in the wild.** The public ceiling is RSA-250, an 829-bit *challenge* number that took about 2,700 core-years in 2020 [@rsa250-2020]. And yet real RSA keys, sessions, and signatures fell anyway, at three layers the RSA math depends on but does not control. **Key generation:** the 2008 Debian OpenSSL bug collapsed the keyspace to roughly 32,767 seeds [@cve-2008-0166]; low-entropy devices *shared primes* recoverable with a single `gcd` in 2012 [@factorable-2012]; Infineon&apos;s ROCA library built *structured* primes a Coppersmith lattice factors, forging Estonian eID cards and YubiKey 4 in 2017 [@roca-crocs]. **Padding:** Bleichenbacher&apos;s 1998 oracle decrypts a session from about a million padding-validity answers, recovering no key and factoring nothing [@bleichenbacher-1998], revived by DROWN across roughly a third of HTTPS via SSLv2 in 2016 [@drown-2016] and by ROBOT on Facebook and PayPal in 2017 [@robotattack]. **Negotiation:** FREAK forced a 512-bit export key and factored it for about \$100 in 2015 [@freakattack]. Seven deployed breaks, one pattern. Where three of them ended in a factorization, it was only because the deployment had already produced a *weak* modulus -- the math merely finished the job. Every fix changed how RSA is generated, used, or negotiated -- validated entropy, OAEP and PSS, TLS 1.3 dropping RSA key transport [@rfc8446] -- never RSA itself.
&lt;h2&gt;1. No One Has Ever Factored a Strong RSA Key. Your RSA Keys Broke Anyway.&lt;/h2&gt;
&lt;p&gt;The largest RSA key ever factored in public is a 250-digit, 829-bit challenge number, and cracking it in 2020 took the world&apos;s best number theorists about 2,700 core-years [@rsa250-2020]. No strong, correctly generated RSA key protecting real traffic has ever been factored at all. Hold that fact still for a moment, because the next one refuses to sit beside it.&lt;/p&gt;
&lt;p&gt;In the same era, Estonian national ID cards were forgeable [@roca-crocs], roughly a third of all HTTPS was decryptable [@drown-2016], hundreds of thousands of embedded private keys were recoverable [@factorable-2012], and export-grade sessions were silently man-in-the-middled [@freakattack]. Every one of those was an RSA break. Not one of them was a factored strong modulus. So how does a factoring problem nobody can beat on a strong key keep producing forged signatures, decrypted sessions, and stolen private keys?&lt;/p&gt;
&lt;p&gt;The resolution is the whole argument of this article, and it belongs before the evidence: none of these broke strong RSA. RSA is a trapdoor permutation, and its single security promise is narrow -- that factoring a strong $n = p \cdot q$ is hard [@rsa-1978]. That promise silently depends on three things the mathematics does not control: where the primes came from, how the decrypting party validates padding, and which key strength a protocol will accept. It was those three deployment-owned layers, never the factoring problem, that gave way.&lt;/p&gt;
&lt;p&gt;So carry one diagnostic question through everything that follows. When an RSA key, session, or signature falls, do not ask &quot;did someone factor the modulus?&quot; Ask instead: which layer the math depends on but does not control gave way -- the primes, the padding, or the negotiation? Every break in this article is a non-empty answer to that question, and you will learn to drop ROCA, DROWN, FREAK, and tomorrow&apos;s incident into it on sight.&lt;/p&gt;

To break RSA in the field, you never factor a strong modulus. You take the weak one the deployment already handed you, or the oracle it left open -- and the factoring problem on a strong key stands untouched.
&lt;p&gt;The paradox even has a face. The researcher Nadia Heninger appears at both poles of this story: she recovered weak keys in the field, and she co-holds the public record for factoring a strong one [@factorable-2012, @rsa250-2020]. One person, both ends of the argument -- the weak keys that fall in an afternoon and the strong one that costs millennia of compute.&lt;/p&gt;
&lt;p&gt;This is Part 3 of &lt;em&gt;How It Breaks in Real Life&lt;/em&gt;, a series with one recurring thesis: the primitive&apos;s mathematics almost never caused the break; the deployment did. RSA is one of its cleanest cases. It has a companion piece, &lt;em&gt;How RSA Would Break: Why Factoring Is the Slow Path and Coppersmith Is the Fast One&lt;/em&gt;, which handles the would-break-in-theory math: the Number Field Sieve, Coppersmith&apos;s lattices, and Shor&apos;s algorithm. This article is the did-break-in-the-field frame.&lt;/p&gt;
&lt;p&gt;If no strong modulus was ever factored and the factoring problem never moved, then everything that broke was built around it. To see how &quot;unfactored modulus&quot; and &quot;recovered key&quot; can both be true at once, we have to go back to what RSA actually promises -- and, more importantly, what it silently assumes.&lt;/p&gt;
&lt;h2&gt;2. What RSA Actually Assumes&lt;/h2&gt;
&lt;p&gt;In 1977, Ron Rivest, Adi Shamir, and Leonard Adleman handed the world its first practical public-key cryptosystem: a public modulus $n = p \cdot q$, a public exponent $e$, a private exponent $d$, and a security argument resting on the hardness of factoring $n$ [@rsa-1978]. That assumption is exactly why the core problem has held for five decades. It is also exactly why, when RSA breaks in the field, the fault lies somewhere else. Being a trapdoor permutation made RSA a primitive. It did not make it a safe deployment.&lt;/p&gt;
&lt;p&gt;Start with the single most important distinction in this article. RSA-the-primitive is a keyed permutation over the integers modulo $n$, and its security promise is narrow and conditional. Here is the precise version, because the imprecise version is the source of half the confusion in the subject.&lt;/p&gt;

RSA fixes a modulus $n = p \cdot q$, a public exponent $e$, and a private exponent $d$; encryption is $c = m^e \bmod n$ and decryption is $m = c^d \bmod n$. Its security rests on the assumption that inverting the permutation without $d$ is hard. Factoring $n$ is *sufficient* to invert it -- recover $\phi(n)$, then $d$ -- and remains the best known attack, so breaking RSA is *at most* as hard as factoring. Whether it is *as hard as* factoring, the RSA-problem-versus-factoring equivalence, is an open question [@boneh-1999, @rsa-1978]. The math controls only that hardness, never where $p$ and $q$ come from.
&lt;p&gt;Read that twice, because the direction matters. Factoring is &lt;em&gt;sufficient&lt;/em&gt; to break RSA: if you can factor $n$, you win. But nobody has proved the reverse, that breaking RSA requires factoring. The RSA problem might, in principle, be easier than factoring; we do not know. What we do know is that the best attack anyone has found on the primitive is still factoring, and factoring a strong modulus is where the wall stands.&lt;/p&gt;
&lt;p&gt;Notice the quiet consequence: the field never even needed that unproven shortcut. Every deployed break in this article bypassed both directions entirely. No one factored a strong key, and no one found a clever inversion either. They walked around the math.&lt;/p&gt;
&lt;p&gt;Because the guarantee is only about the &lt;em&gt;difficulty of factoring&lt;/em&gt;, it says nothing about three other things:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Where $p$ and $q$ come from.&lt;/strong&gt; They must be large, independent, and drawn from &lt;a href=&quot;https://paragmali.com/blog/predictable-or-repeated-the-only-two-ways-cryptographic-rand/&quot; rel=&quot;noopener&quot;&gt;real entropy&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;How the raw permutation is wrapped.&lt;/strong&gt; Textbook RSA is deterministic and malleable, so it must be padded before it is used.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Which key strength a protocol will accept.&lt;/strong&gt; A 512-bit option nobody wants is still a 512-bit option somebody can force.&lt;/li&gt;
&lt;/ol&gt;

Unpadded RSA is deterministic: equal plaintexts always produce equal ciphertexts, so an eavesdropper can recognize repeats and test guesses. It is also *malleable*, because $(m_1^e)(m_2^e) \equiv (m_1 m_2)^e \pmod{n}$: an attacker who multiplies a ciphertext by $s^e$ turns it into an encryption of $m \cdot s$ without knowing $m$. Both properties make raw RSA unusable on its own; it must be wrapped in a padding scheme. That same malleability is the lever every padding-oracle attack pulls.
&lt;p&gt;Those three silences are the structure of the whole article. Picture them as a map: the factoring-hardness assumption sits at the center, and three deployment-owned dependencies surround it. Each is an independent surface an attacker can reach without ever touching the factoring problem.&lt;/p&gt;

flowchart TD
    A[&quot;The one RSA guarantee: factoring a strong modulus is hard&quot;]
    A --&amp;gt; B[&quot;Layer 1: Key generation, where the primes come from&quot;]
    A --&amp;gt; C[&quot;Layer 2: Padding validation, how decryption is checked&quot;]
    A --&amp;gt; D[&quot;Layer 3: Negotiation, which key strength is accepted&quot;]
    B --&amp;gt; E[&quot;Each layer is an independent failure surface reached without factoring&quot;]
    C --&amp;gt; E
    D --&amp;gt; E
&lt;p&gt;Before we watch each dependency fail, three guardrails, because the argument is easy to overstate in exactly three ways.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; The whole argument turns on one distinction. Three incidents ahead -- FREAK, ROCA, and shared primes -- &lt;em&gt;do&lt;/em&gt; end in a factorization. That is not a counterexample to the thesis; it is the thesis. In each case the deployment first produced a &lt;em&gt;weak&lt;/em&gt; modulus (a downgrade forced 512 bits, a library constrained the primes, bad entropy made two keys collide), and the math only finished a job the deployment had already set up. The precise, load-bearing claim, kept word-for-word throughout: no strong, correctly generated, deployed modulus has ever been factored in the field. The public ceiling is RSA-250, an 829-bit challenge number [@rsa250-2020].&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The series this article belongs to lists a familiar set of culprits: the RNG, the nonce, the padding, the downgrade. RSA answers to most of them, but not the nonce.RSA signing has no secret per-signature nonce whose reuse leaks the key. &lt;a href=&quot;https://paragmali.com/blog/one-number-used-twice-how-a-repeated-nonce-hands-over-your-p/&quot; rel=&quot;noopener&quot;&gt;The nonce-reuse break&lt;/a&gt; -- the reused $k$ behind the 2010 PlayStation 3 and 2013 Android SecureRandom key recoveries [@ps3-epic-fail-2010, @android-securerandom-2013, @bitcoin-android-alert-2013] -- is an (EC)DSA phenomenon, a different primitive. There is no &quot;RSA nonce-reuse break&quot;; when you see one described, it is really an ECDSA story, and it is told in its own article. Two of the three real culprits are RSA&apos;s, and both are downstream of the map above.&lt;/p&gt;

The series says the primitive&apos;s math *almost* never causes the break, and that hedge is load-bearing. Factoring is genuinely real, just slow. It fell RSA-155 (512-bit) in 1999, RSA-768 in 2009, and RSA-250 in 2020 [@rsa155-2000, @rsa768-epfl, @rsa250-2020], every one a public *challenge* number, never a deployed key. And Shor&apos;s algorithm will fall RSA outright on a large quantum computer. None of that is a deployed strong key, and all of it belongs to the companion article, *How RSA Would Break*. State the hedge, and never inflate it into a claim that the mathematics is somehow unbreakable.
&lt;p&gt;One narrow assumption, three silent dependencies, none of them the factoring problem. Before we watch each dependency break in the field, we need to see the contracts up close: what real entropy actually buys, why a padding check is a loaded gun, and why a 512-bit option nobody uses is still a live weapon.&lt;/p&gt;
&lt;h2&gt;3. Three Contracts the Math Cannot Enforce&lt;/h2&gt;
&lt;p&gt;Every field break in this article is the violation of one specific promise -- a promise the RSA math assumes but has no way to enforce. There are exactly three, one per layer of the map, and once you can see the contract, the break becomes obvious. Here they are, side by side.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;The contract it imposes&lt;/th&gt;
&lt;th&gt;Cost of violating it&lt;/th&gt;
&lt;th&gt;Who owns it&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;Key generation&lt;/td&gt;
&lt;td&gt;Primes are unpredictable and never shared&lt;/td&gt;
&lt;td&gt;Keys become enumerable, colliding, or structurally factorable&lt;/td&gt;
&lt;td&gt;The RNG, the key-generation library, the boot-time entropy source&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Padding validation&lt;/td&gt;
&lt;td&gt;Never reveal whether a decryption was well-padded&lt;/td&gt;
&lt;td&gt;The server becomes a decryption or signing oracle&lt;/td&gt;
&lt;td&gt;The TLS and PKCS#1 implementation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Negotiation&lt;/td&gt;
&lt;td&gt;Never accept a crippled key or a dead protocol&lt;/td&gt;
&lt;td&gt;A strong peer is forced down to a breakable one&lt;/td&gt;
&lt;td&gt;The protocol state machine and its configuration&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;The key-generation contract: the primes must be unpredictable and never shared.&lt;/strong&gt; Two &quot;random&quot; primes are only as unpredictable as the generator that produced them. Starve that generator of entropy, cripple it with a bug, or bias its structure, and the primes come out predictable, colliding, or specially formed -- while every later step still looks textbook-correct.&lt;/p&gt;

A cryptographically secure pseudorandom number generator (CSPRNG) stretches a small secret seed into an unbounded stream of unpredictable bits, and its output is only as unpredictable as that seed. If the seed is starved of real entropy at boot, crippled by a bug that removes entropy, or drawn from a generator that constrains its outputs, the primes it produces become predictable, colliding, or specially formed, even though the arithmetic around them is flawless.

If two moduli $n_1$ and $n_2$ accidentally share one prime, then $\gcd(n_1, n_2) = p$ recovers that prime with a single Euclidean gcd, and one division gives the other factor of each. A batch-GCD computes every pairwise gcd across millions of keys at once with a product-and-remainder tree, in quasi-linear time. It is simultaneously an attack (recover private keys at Internet scale) and a defense (scan your own fleet for collisions).
&lt;p&gt;The hidden assumption behind this contract is quiet but total: enough real entropy reached key generation, and no two keys ever drew the same prime.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The padding contract: never reveal whether padding was valid.&lt;/strong&gt; Textbook RSA is deterministic and malleable, so a real system must wrap the message before exponentiating. For twenty-five years the near-universal wrapper has been PKCS#1 v1.5.&lt;/p&gt;

Before exponentiation the message is wrapped as `00 02 [at least 8 random non-zero bytes] 00 [message]` [@rfc2313]. The leading `00 02` marks an encryption block, the random bytes make equal plaintexts encrypt differently, and the `00` separator marks where the message begins. The attack in this article is on this v1.5 *encryption* format, whose validity check becomes the oracle. PKCS#1 v1.5 *signatures* are a separate construction and are not what these attacks break.
&lt;p&gt;Here is the conceptual pivot the whole subject turns on. In 1998, Daniel Bleichenbacher noticed that the padding &lt;em&gt;check itself&lt;/em&gt; is a leak [@bleichenbacher-1998]. A server that decrypts a ciphertext and then reveals -- by error, timeout, or timing -- whether the result began with the required &lt;code&gt;00 02&lt;/code&gt; bytes has answered a yes/no question about the plaintext. One bit does not sound like much. But raw RSA is malleable, so the attacker can ask the question about a plaintext of their choosing.&lt;/p&gt;

A decrypting party that reveals -- by error message, connection reset, timeout, or response timing -- whether a submitted ciphertext decrypted to validly padded plaintext leaks one bit per query. Feed it enough adaptively chosen ciphertexts and those one-bit answers pin the plaintext down, without ever recovering the private key. This is the single most load-bearing idea in the article.
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key idea:&lt;/strong&gt; Because raw RSA is malleable, an attacker can multiply a ciphertext $c$ by $s^e$ to form $c&apos; = c \cdot s^e \bmod n$, which decrypts to $m \cdot s \bmod n$. A server that reveals whether each such $c&apos;$ was validly padded answers one yes/no question per query about where $m$ lies. About $10^6$ adaptive queries narrow $m$ to a single value: a session decrypted, with no private key recovered and nothing factored. This one mechanic drives Bleichenbacher, DROWN, and ROBOT.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;You can feel the mechanic in miniature. The toy below uses an idealized one-bit oracle -- it reports only whether the decryption falls in the bottom half of the range -- and binary-searches the plaintext out of the server without ever touching the key.The oracle recovers no private key and factors nothing; it turns the server into a one-session decryption (or one-message signing) oracle, and the modulus is untouched afterward. Keep two things distinct: the attack is on PKCS#1 v1.5 &lt;em&gt;encryption&lt;/em&gt;, and v1.5 &lt;em&gt;signatures&lt;/em&gt; are a separate scheme it does not break.&lt;/p&gt;
&lt;p&gt;{`
function modpow(b, e, n){ b %= n; let r = 1n; while (e &amp;gt; 0n){ if (e &amp;amp; 1n) r = (r&lt;em&gt;b)%n; b = (b&lt;/em&gt;b)%n; e &amp;gt;&amp;gt;= 1n; } return r; }
const p = 61n, q = 53n, n = p*q;          // toy modulus n = 3233
const e = 17n, d = 2753n;                 // public e, private d
const m = 65n;                            // the secret plaintext
const c = modpow(m, e, n);                // the ciphertext the attacker holds&lt;/p&gt;
&lt;p&gt;// The ONLY leak: does the decryption land in the bottom half of [0, n)?
// An idealized magnitude / MSB oracle -- one bit per query.
const oracle = (ct) =&amp;gt; modpow(ct, d, n) &amp;lt; n / 2n;&lt;/p&gt;
&lt;p&gt;const enc2 = modpow(2n, e, n);            // multiplier that doubles the plaintext (malleability)
const K = 14n, SCALE = 1n &amp;lt;&amp;lt; K;
let loS = 0n, hiS = n * SCALE, ct = c;
for (let i = 0n; i &amp;lt; K; i++){
  const mid = (loS + hiS) / 2n;
  if (oracle(ct)) hiS = mid; else loS = mid;   // read one bit, halve the interval
  ct = (ct * enc2) % n;                          // next query sees 2&lt;em&gt;m, 4&lt;/em&gt;m, ... mod n
}
console.log(&apos;recovered plaintext -&amp;gt;&apos;, (hiS / SCALE).toString());   // -&amp;gt; 65&lt;/p&gt;
&lt;p&gt;// The real PKCS#1 v1.5 oracle asks a different one-bit question: do the top bytes
// equal 00 02? That narrows m onto a union of intervals 2B &amp;lt;= m &amp;lt; 3B rather than a
// &amp;lt; n/2 half -- same principle, different bit. This &amp;lt; n/2 magnitude test is the same
// family of magnitude / most-significant-byte leak Manger&apos;s 2001 attack exploits
// against OAEP -- but his oracle tests x &amp;lt; B = 2^(8(k-1)) (whether the leading octet
// is zero), not x &amp;lt; n/2.
`}&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The negotiation contract: never accept a deliberately crippled key or a dead protocol.&lt;/strong&gt; The math cannot see which key strength a handshake settled on, nor whether the protocol carrying it was retired a decade ago.&lt;/p&gt;

1990s US export regulations capped exportable cryptography at deliberately weak strengths, including 512-bit `RSA_EXPORT` key exchange, and SSLv2 was the era&apos;s now-obsolete protocol [@freakattack, @freak-sp2015]. Both were retired in principle long ago, yet both survived in shipping code and server configurations long enough to become live attack surfaces.
&lt;p&gt;The assumption here is that only a party that &lt;em&gt;wants&lt;/em&gt; export-grade crypto, or &lt;em&gt;wants&lt;/em&gt; SSLv2, will ever get it.&lt;/p&gt;
&lt;p&gt;Three contracts, each reasonable, each unenforced by the math. The factoring problem cannot tell whether you fed it a shared prime, a leaky padding check, or a forced 512-bit key. Which raises the only question that matters: on real smartcards and real servers, do these contracts actually hold? They do not. Here is where, when, and how each one broke.&lt;/p&gt;
&lt;h2&gt;4. Three Deployment Failures, Three Layers, One Pattern&lt;/h2&gt;
&lt;p&gt;The comfortable belief is &quot;we use RSA-2048, so our keys, sessions, and signatures are safe.&quot; What follows are seven independent refutations of that inference: seven deployed field breaks, grouped into three pillars, one per layer the math depends on but does not control. Each pillar runs in chronological order, but read it as a failure catalog, not a lineage of improving designs. They are three simultaneous layers every real deployment must secure at once, and the chronology is simply the attacker&apos;s frontier moving from one unmet obligation to the next as each was patched.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Incident&lt;/th&gt;
&lt;th&gt;Year&lt;/th&gt;
&lt;th&gt;Pillar&lt;/th&gt;
&lt;th&gt;Mechanism&lt;/th&gt;
&lt;th&gt;Modulus&lt;/th&gt;
&lt;th&gt;Recovers key?&lt;/th&gt;
&lt;th&gt;Factors it?&lt;/th&gt;
&lt;th&gt;The fix (changed the deployment)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;Debian OpenSSL RNG&lt;/td&gt;
&lt;td&gt;2008&lt;/td&gt;
&lt;td&gt;Key generation&lt;/td&gt;
&lt;td&gt;Seed collapsed to the process ID; keys enumerable&lt;/td&gt;
&lt;td&gt;Weak&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Real entropy; regenerate every key&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mining Your Ps and Qs&lt;/td&gt;
&lt;td&gt;2012&lt;/td&gt;
&lt;td&gt;Key generation&lt;/td&gt;
&lt;td&gt;Shared prime; one gcd factors both&lt;/td&gt;
&lt;td&gt;Weak&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes (weak)&lt;/td&gt;
&lt;td&gt;Entropy at boot; batch-GCD scanning&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ROCA&lt;/td&gt;
&lt;td&gt;2017&lt;/td&gt;
&lt;td&gt;Key generation&lt;/td&gt;
&lt;td&gt;Structured primes; Coppersmith lattice&lt;/td&gt;
&lt;td&gt;Weak&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes (weak)&lt;/td&gt;
&lt;td&gt;Fix the library; ROCA detector; regenerate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bleichenbacher&lt;/td&gt;
&lt;td&gt;1998&lt;/td&gt;
&lt;td&gt;Padding&lt;/td&gt;
&lt;td&gt;v1.5 padding-validity oracle&lt;/td&gt;
&lt;td&gt;Strong&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Uniform errors; OAEP; drop RSA transport&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DROWN&lt;/td&gt;
&lt;td&gt;2016&lt;/td&gt;
&lt;td&gt;Padding&lt;/td&gt;
&lt;td&gt;Same oracle via a shared-key SSLv2 endpoint&lt;/td&gt;
&lt;td&gt;Strong&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Disable SSLv2; stop cross-protocol key reuse&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ROBOT&lt;/td&gt;
&lt;td&gt;2017&lt;/td&gt;
&lt;td&gt;Padding&lt;/td&gt;
&lt;td&gt;The 1998 oracle still answering across nine vendors&lt;/td&gt;
&lt;td&gt;Strong&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Constant-time uniform errors; retire RSA encryption&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FREAK&lt;/td&gt;
&lt;td&gt;2015&lt;/td&gt;
&lt;td&gt;Negotiation&lt;/td&gt;
&lt;td&gt;Downgrade to 512-bit &lt;code&gt;RSA_EXPORT&lt;/code&gt;, then factor&lt;/td&gt;
&lt;td&gt;Weak&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes (weak)&lt;/td&gt;
&lt;td&gt;Remove export ciphers; downgrade protection; TLS 1.3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RSA-250 (ceiling)&lt;/td&gt;
&lt;td&gt;2020&lt;/td&gt;
&lt;td&gt;Challenge&lt;/td&gt;
&lt;td&gt;GNFS on an 829-bit challenge number&lt;/td&gt;
&lt;td&gt;Strong&lt;/td&gt;
&lt;td&gt;n/a&lt;/td&gt;
&lt;td&gt;Yes (challenge)&lt;/td&gt;
&lt;td&gt;Not a field break; the public ceiling, never deployed&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Read the two rightmost data columns as the argument in a grid: every row that ends in a factored modulus is a &lt;em&gt;weak&lt;/em&gt; modulus, and every row with a &lt;em&gt;strong&lt;/em&gt; modulus recovers no key and factors nothing. The one strong modulus ever factored, RSA-250, is a challenge number that never protected anything.&lt;/p&gt;

flowchart LR
    Y1998[&quot;1998 Bleichenbacher, padding&quot;] --&amp;gt; Y2008[&quot;2008 Debian RNG, key generation&quot;]
    Y2008 --&amp;gt; Y2012[&quot;2012 shared primes, key generation&quot;]
    Y2012 --&amp;gt; Y2015[&quot;2015 FREAK, negotiation&quot;]
    Y2015 --&amp;gt; Y2016[&quot;2016 DROWN via SSLv2, padding&quot;]
    Y2016 --&amp;gt; Y2017[&quot;2017 ROCA structure, and ROBOT padding&quot;]
    Y2017 --&amp;gt; Y2020[&quot;2020 RSA-250 ceiling, a challenge number&quot;]
&lt;h3&gt;Pillar 1: The primes were weak before the math began&lt;/h3&gt;
&lt;p&gt;All three key-generation breaks share one shape: generation produced a weak modulus, and then -- only then -- arithmetic finished it off. A strong modulus is on none of these paths.&lt;/p&gt;

flowchart TD
    R[&quot;Crippled RNG, Debian 2008&quot;] --&amp;gt; W[&quot;Weak modulus&quot;]
    B[&quot;Starved boot entropy&quot;] --&amp;gt; SP[&quot;Two keys share a prime, 2012&quot;]
    SP --&amp;gt; G[&quot;One gcd recovers the shared prime&quot;]
    G --&amp;gt; W
    ST[&quot;Structured primes, Infineon RSALib, 2017&quot;] --&amp;gt; CO[&quot;Coppersmith lattice factors the key&quot;]
    CO --&amp;gt; W
    W --&amp;gt; F[&quot;The math finishes the job, and a strong modulus is on no path here&quot;]
&lt;p&gt;&lt;strong&gt;Debian OpenSSL, defect 2006, disclosed 2008.&lt;/strong&gt; A well-meaning Debian patch, written to silence a Valgrind warning about uninitialized memory, removed most of the entropy feeding OpenSSL&apos;s PRNG. The change shipped in openssl 0.9.8c-1, and for nearly two years the effective seed collapsed to essentially the process ID, about 32,767 possibilities [@cve-2008-0166, @debian-dsa1571]. Keys stopped being unpredictable and became &lt;em&gt;enumerable&lt;/em&gt;: an attacker could precompute the whole set. No factoring, no oracle, just a keyspace small enough to list.The defect was uploaded as openssl 0.9.8c-1 on 17 September 2006 and disclosed on 13 May 2008 by Luciano Bello [@debian-dsa1571-full]. Field measurement came from Yilek and colleagues at IMC 2009, who watched the fix propagate: 751 vulnerable certificates observed, and a meaningful fraction still vulnerable roughly six months later [@yilek-imc2009].&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Mining Your Ps and Qs, 2012.&lt;/strong&gt; Nadia Heninger, Zakir Durumeric, Eric Wustrow, and J. Alex Halderman scanned the entire IPv4 Internet&apos;s TLS and SSH hosts and found something worse than predictability. They found collision. Headless and embedded devices, generating keys at first boot with almost no entropy, sometimes produced moduli that shared a single prime with an unrelated device. A shared prime is fatal, because $\gcd(n_1, n_2) = p$ factors both moduli in the time of one gcd.&lt;/p&gt;
&lt;p&gt;They measured that 5.57% of TLS hosts shared keys, and they remotely recovered the RSA private keys of 0.50% of all TLS hosts through common factors, notifying 54 manufacturers [@factorable-2012]. An independent study the same year, titled &quot;Ron was wrong, Whit is right,&quot; found the same collisions in a different dataset and ruled out a single-vendor fluke [@ron-wrong-2012].&lt;/p&gt;
&lt;p&gt;You can watch both keys fall from one gcd:&lt;/p&gt;
&lt;p&gt;{&lt;code&gt;const gcd = (a, b) =&amp;gt; { while (b) { [a, b] = [b, a % b]; } return a; }; // Two RSA moduli from two different devices. Neither looks weak on its own. // (Shrunk stand-ins; real moduli are hundreds of digits and the gcd costs the same.) const n1 = 63900000000000000000000000000000000000000000002625100000000000000000000000000000000000000000026931n; const n2 = 74700000000000000000000000000000000000000000003196300000000000000000000000000000000000000000034189n; const p = gcd(n1, n2);              // one Euclidean gcd -- the entire attack const q1 = n1 / p, q2 = n2 / p; console.log(&apos;shared prime p =&apos;, p.toString()); console.log(&apos;device 1 recovered:&apos;, p * q1 === n1); console.log(&apos;device 2 recovered:&apos;, p * q2 === n2); console.log(&apos;Both private keys recovered from one gcd. Key size was irrelevant.&apos;);&lt;/code&gt;}&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;ROCA, 2017.&lt;/strong&gt; The first two breaks needed bad randomness. ROCA needed none. Matus Nemec, Marek Sys, Petr Svenda, Dusan Klinec, and Vashek Matyas showed that Infineon&apos;s RSALib built its primes in a speed-optimizing but constrained form, $p = k \cdot M + (65537^a \bmod M)$, detectable from the &lt;em&gt;public key alone&lt;/em&gt; and factorable by a Coppersmith-style lattice far faster than general factoring [@roca-ccs2017, @roca-crocs, @cve-2017-15361]. The randomness was fine; the &lt;em&gt;structure&lt;/em&gt; was the flaw, so every key the chip ever generated was affected.The first author is Matus Nemec (not &quot;Miroslav&quot;), and the fifth is Vashek, or Vaclav, Matyas (not &quot;Vladimir&quot;). The affected products were the YubiKey 4 (not the YubiKey 5), Estonian national eID cards, and Infineon-based TPMs. Because the attack keys off structure rather than entropy, it is RNG-independent [@roca-crocs].&lt;/p&gt;

For a monic polynomial of degree $d$ modulo $N$, Coppersmith&apos;s method finds every integer root $x_0$ with $\lvert x_0 \rvert \le N^{1/d}$ in polynomial time, using lattice reduction [@coppersmith-1997]. When a prime is built with a special constrained structure, part of it becomes such a small root, and the method factors $n$ far faster than general-purpose factoring. It is the dormant 1997 engine ROCA revived; the lattice details belong to the companion *How RSA Would Break*.
&lt;p&gt;The cost, from the authors themselves: a 1024-bit key fell in under three CPU-months, about $76 on AWS; a 2048-bit key in under 100 CPU-years, about $40,000; and roughly 760,000 confirmed-vulnerable keys were in the field [@roca-crocs]. Notice what &quot;2048-bit&quot; bought here: nothing. The number that mattered was not the key size but the structure of the primes.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; ROCA factors nominally 2048-bit keys, and a shared prime falls to one gcd, yet both only work because &lt;em&gt;generation&lt;/em&gt; produced a weak key. The factoring problem on a strong modulus never bent; the RNG or the library did. This is the thesis&apos;s sharpest edge, and it is why the weak-versus-strong distinction has to stay explicit: a factored key here is evidence &lt;em&gt;for&lt;/em&gt; the thesis, not against it.&lt;/p&gt;
&lt;/blockquote&gt;

Infineon&apos;s ROCA-vulnerable chips were FIPS 140-2 and Common Criteria EAL5+ certified, and they passed for years. Certification tested the on-chip RNG, not the *structure* of the public keys it produced, so a biased-but-well-randomized generator sailed straight through. This is exactly why the Pillar 1 fix adds post-generation structure and shared-factor tests, not just entropy checks [@roca-crocs].
&lt;h3&gt;Pillar 2: The padding check was an oracle&lt;/h3&gt;
&lt;p&gt;Now the padding pivot from Section 3, shown surviving twenty-five years of patches. Three incidents, one bug, reappearing at a new layer each time an inner defense hardened.&lt;/p&gt;

sequenceDiagram
    participant A as Attacker
    participant S as Server
    Note over S: Holds the private key, reveals only whether padding was valid
    A-&amp;gt;&amp;gt;S: Submit c times s^e mod n
    S-&amp;gt;&amp;gt;A: One bit, padding valid or invalid
    Note over A,S: Each bit narrows the interval that must contain m
    A-&amp;gt;&amp;gt;S: Submit the next adaptively chosen ciphertext
    S-&amp;gt;&amp;gt;A: One more bit
    Note over A,S: After about a million queries m is pinned, key never touched
&lt;p&gt;&lt;strong&gt;Bleichenbacher, 1998.&lt;/strong&gt; The origin. A PKCS#1 v1.5 encryption endpoint that reveals whether a decryption was well-padded becomes an adaptive chosen-ciphertext decryption oracle, and about $10^6$ queries decrypt a captured session. The private key is never recovered and nothing is factored; the attacker simply borrows the server&apos;s decryption ability for one message [@bleichenbacher-1998].&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;DROWN, 2016.&lt;/strong&gt; Nimrod Aviram and fourteen coauthors, Heninger and Halderman among them, showed the 1998 oracle had a door nobody had closed: a forgotten SSLv2 endpoint. If a modern TLS server shared its RSA key with an old SSLv2 server -- common, because operators reused certificates -- an attacker could run a cross-protocol Bleichenbacher attack through the SSLv2 side, whose export ciphers and an OpenSSL bug made a &quot;special&quot; variant cheap.&lt;/p&gt;
&lt;p&gt;At disclosure in March 2016, 33% of all HTTPS servers, 25% of the top million, and 22% of browser-trusted sites were vulnerable; it fell to about 1.2% by 2019 [@drown-2016].DROWN is not ROBOT. DROWN (2016) reached modern TLS &lt;em&gt;cross-protocol&lt;/em&gt;, through a shared-key SSLv2 endpoint. ROBOT (disclosed December 2017, published at USENIX Security 2018) found the &lt;em&gt;same&lt;/em&gt; oracle still answering &lt;em&gt;directly&lt;/em&gt; on modern TLS stacks. Different years, different vector, the same 1998 bug [@drown-2016, @robotattack].&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Nadia Heninger co-authored &lt;em&gt;Mining Your Ps and Qs&lt;/em&gt; (2012, weak keys recovered by gcd) and &lt;em&gt;DROWN&lt;/em&gt; (2016, the oracle re-armed via SSLv2), and she co-holds the &lt;em&gt;RSA-250&lt;/em&gt; factoring record (2020, the slow way a &lt;em&gt;strong&lt;/em&gt; key falls) [@factorable-2012, @drown-2016, @rsa250-2020]. One researcher stands at both poles of the argument. She is not alone: Juraj Somorovsky bridges DROWN and ROBOT, and J. Alex Halderman bridges Mining Your Ps and Qs and DROWN. This is one research community with a shared toolkit of Internet-wide measurement, not rival schools [@robotattack].&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;ROBOT, 2017.&lt;/strong&gt; Hanno Bock, Juraj Somorovsky, and Craig Young gave the bug its name, Return Of Bleichenbacher&apos;s Oracle Threat, and showed the nineteen-year-old oracle still live across nine vendors, including F5, Citrix, Radware, Palo Alto Networks, IBM, and Cisco, with vulnerable subdomains on 27 of the top 100 domains, Facebook and PayPal among them [@robot-usenix2018, @robot-eprint, @robotattack]. To prove the point without crossing a line, the team used the oracle to sign a message with Facebook&apos;s private key, and still never recovered that key.&lt;/p&gt;

ROBOT &quot;allows performing RSA decryption and signing operations with the private key of a TLS server&quot; -- and yet recovers no private key at all. Its own recommendation is the entire fix thesis in one line: disable RSA encryption cipher suites entirely.
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Not by error text, not by an alert code, not by a TCP reset, not by a timeout, not by response timing. You cannot make an observable-validity scheme uniformly safe by patching. You replace it (OAEP) or remove it (no RSA key transport).&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;All three padding incidents carry the sharpest version of the thesis: the attacker &lt;em&gt;uses&lt;/em&gt; the private key without ever &lt;em&gt;holding&lt;/em&gt; it, and the modulus is untouched at the end. The remedy was never a bigger key. It was uniform, constant-time error handling and, ultimately, retiring RSA encryption in favor of OAEP or &lt;a href=&quot;https://paragmali.com/blog/nobody-broke-the-discrete-log-a-field-guide-to-diffie-hellma/&quot; rel=&quot;noopener&quot;&gt;forward-secret key exchange&lt;/a&gt;, exactly as ROBOT advised.&lt;/p&gt;
&lt;h3&gt;Pillar 3: The negotiation forced a crippled key&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;FREAK, 2015.&lt;/strong&gt; Benjamin Beurdouche and seven coauthors found a TLS state-machine flaw with a nasty consequence. A man-in-the-middle could force a handshake down to 512-bit &lt;code&gt;RSA_EXPORT&lt;/code&gt; even when neither the client nor the server wanted export-grade crypto. And 512-bit RSA has been factorable since RSA-155 fell in 1999, so the attacker factors the downgraded modulus in hours for about $100 on cloud compute, then impersonates or decrypts the session. At disclosure, 36.7% of browser-trusted HTTPS servers still accepted export RSA [@freak-sp2015, @freak-pdf, @freakattack, @cve-2015-0204, @rsa155-2000].&lt;/p&gt;

sequenceDiagram
    participant C as Client
    participant M as MITM
    participant S as Server
    C-&amp;gt;&amp;gt;M: ClientHello, wants strong RSA
    M-&amp;gt;&amp;gt;S: Forward it but ask for RSA_EXPORT
    S-&amp;gt;&amp;gt;M: ServerKeyExchange with a 512-bit export key
    M-&amp;gt;&amp;gt;C: Inject the 512-bit key as if it were normal
    Note over M: Factor the 512-bit modulus in hours for about 100 dollars
    M-&amp;gt;&amp;gt;C: Impersonate or decrypt the session
&lt;p&gt;The factoring step is feasible here only because a downgrade plus 1990s export policy forced a deliberately weak key. The math finished a job the deployment set up, and it could not have touched a 2048-bit key the same way.The academic paper, &quot;A Messy State of the Union,&quot; has eight authors; never attribute FREAK to a single name [@freak-sp2015]. The freakattack.com credit to Karthikeyan Bhargavan and the miTLS team, with tracking by the University of Michigan, is a separate and separately-true fact [@freakattack]. The fix was, again, a deployment change, not a cryptographic one: remove export ciphers, disable SSLv2, add downgrade protection, and ultimately TLS 1.3 dropping RSA key exchange entirely.&lt;/p&gt;
&lt;p&gt;Three layers, seven field breaks, one shape. Every time, a contract the RSA math depends on but does not control was violated, real keys or sessions or signatures fell, and the factoring problem on a strong modulus did not move. Seen one at a time, each looks like a smartcard bug, a TLS bug, a legacy-protocol bug. Seen together, they are a single pattern, and the pattern is the whole point.&lt;/p&gt;
&lt;h2&gt;5. Every Fix Changed How RSA Is Generated, Used, or Negotiated -- Never RSA&lt;/h2&gt;
&lt;p&gt;Stop treating the seven incidents as separate. Line them up and one realization collapses the subject: every field break attacked a layer the math depends on but does not control -- entropy, then the padding check, then negotiated strength -- and every fix changed how RSA is &lt;em&gt;generated, used, or negotiated&lt;/em&gt;, never the factoring problem.&lt;/p&gt;
&lt;p&gt;The breakthrough here is not a eureka discovery. It is an engineering discipline, visible only because the same shape repeats across a twenty-year drumbeat from 1998 to 2020. Map each fix to its layer and watch none of them touch the primitive:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Key generation&lt;/strong&gt; was answered with validated high-entropy generation plus post-hoc structure and shared-factor testing: FIPS 186-5, batch-GCD scanners, and the ROCA detector [@fips186-5, @factorable-2012, @roca-crocs].&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Padding&lt;/strong&gt; was answered with OAEP, uniform constant-time error handling, and, most durably, structurally retiring RSA encryption [@rfc8017, @robotattack].&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Negotiation&lt;/strong&gt; was answered by removing export and SSLv2, adding downgrade protection, and TLS 1.3 deleting static RSA key exchange outright [@rfc8446].&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It is worth dwelling on the padding fix that &lt;em&gt;failed&lt;/em&gt;, because its failure is the most instructive event in the whole story. The canonical countermeasure was not hand-waving. TLS 1.2 specified it precisely: on any decryption or padding failure, the server does not signal an error at all. It substitutes a randomly generated premaster secret and proceeds as if nothing were wrong, so the handshake fails uniformly and only later, at the &lt;code&gt;Finished&lt;/code&gt;-message MAC, with no observable difference between valid and invalid padding [@rfc5246]. The RFC even cites Bleichenbacher and the Klima-Pokorny-Rosa version oracle by name.&lt;/p&gt;
&lt;p&gt;It removed exactly one channel, the explicit error message. And the same one bit resurfaced through three more: a forgotten SSLv2 sibling (DROWN), implementation quirks like TCP resets and alert timing (ROBOT), and decryption timing (Marvin) [@drown-2016, @robotattack, @marvin-paper].&lt;/p&gt;
&lt;p&gt;That is the deep lesson. A scheme whose &lt;em&gt;validity is observable&lt;/em&gt; cannot be patched uniformly safe; it can only be structurally replaced. The field kept relearning Bleichenbacher&apos;s single lesson at a new layer each time an inner defense hardened.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key idea:&lt;/strong&gt; Every field break attacked a layer the math depends on but does not control, and every remedy -- validated entropy and structure tests, OAEP and PSS with uniform errors, dead export and SSLv2, TLS 1.3 -- hardened a &lt;em&gt;deployment obligation&lt;/em&gt;. The factoring problem is the fixed point around which everything else evolved. Soundness requires the weakest of three deployment layers to hold, and hardening any one of them is an exercise in &lt;em&gt;usage&lt;/em&gt;, not cryptanalysis.&lt;/p&gt;
&lt;/blockquote&gt;

This is exactly the border with the two companion pieces. The would-break-in-theory math -- the Number Field Sieve, Coppersmith&apos;s lattices, Shor&apos;s algorithm -- belongs to *How RSA Would Break*. The constructive depth of OAEP and PSS, including how to implement the constant-time error path correctly, belongs to [*RSA Done Right*](/blog/rsa-is-a-trapdoor-not-a-cryptosystem-oaep-pss-and-the-25-yea/). This article links out to both rather than re-teaching them, because its job is the pattern across the field, not the internals of any one fix.
&lt;p&gt;If every fix is &quot;generate, use, or negotiate RSA correctly,&quot; then the state of the art is simply the catalog of what &quot;correctly&quot; means at each layer in 2026 -- and where, even now, correct-by-the-book still is not enough.&lt;/p&gt;
&lt;h2&gt;6. What Correct RSA Deployment Looks Like in 2026&lt;/h2&gt;
&lt;p&gt;The modern answer is unglamorous, and that is the point: be sound at all three layers at once, because validated key generation does nothing for a leaky padding check, and a perfect padding scheme does nothing for a forced 512-bit downgrade.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Key generation.&lt;/strong&gt; Use validated, high-entropy, structure-checked generation per FIPS 186-5, published February 2023 [@fips186-5]. Guarantee real entropy &lt;em&gt;before&lt;/em&gt; the first key is generated, use 2048-bit or larger moduli, and run continuous fleet hygiene: batch-GCD across your own keys and the ROCA detector against certified black boxes [@factorable-2012, @roca-crocs]. Generation and measurement are complementary -- measurement catches what generation missed.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Padding and encryption.&lt;/strong&gt; Use RSA-OAEP for encryption and RSA-PSS for signatures, both from RFC 8017, with constant-time, uniform-error decryption; better still, avoid RSA key transport altogether [@rfc8017].&lt;/p&gt;

OAEP (Optimal Asymmetric Encryption Padding) is a plaintext-aware encryption padding, and PSS is the analogous randomized signature scheme; both are specified in RFC 8017 [@rfc8017]. OAEP removes the Bleichenbacher oracle *as a class*, but only when decryption returns one constant-time, generic error for every failure. OAEP decoding has two internally distinct failure cases -- an integer-out-of-range or wrong leading octet, versus an octet-format or integrity failure -- and if an implementation lets them be distinguishable by code, alert, or timing, OAEP itself becomes a padding oracle: Manger&apos;s 2001 attack then recovers the plaintext in about $\log_2 n$ queries, far fewer than Bleichenbacher&apos;s $10^6$, because the leaked bit is cleaner [@manger-2001]. PSS is not what the padding oracles break; construction depth belongs to *RSA Done Right*.
&lt;p&gt;The one-line takeaway is worth memorizing: OAEP done wrong is Bleichenbacher wearing different padding. Even the fix is a deployment obligation.Marvin (2023, Hubert Kario at Red Hat) is a modern &lt;em&gt;timing&lt;/em&gt; revival of the v1.5 oracle across many libraries. Its existence and authorship are verified, but keep it at mention weight pending venue and peer-review confirmation, rather than leaning on it as a load-bearing result [@marvin-paper, @marvin-iacr-news].&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Negotiation.&lt;/strong&gt; Use TLS 1.3, which removes static RSA key exchange entirely: all key exchange is forward-secret (EC)DHE, and RSA survives only as a &lt;em&gt;signature&lt;/em&gt; algorithm, with the rationale spelled out in the RFC&apos;s Appendix E.8, &quot;Attacks on Static RSA&quot; [@rfc8446]. SSLv2, export ciphers, and &lt;code&gt;RSA_EXPORT&lt;/code&gt; are dead. Forward secrecy is the structural win here: with no RSA key transport on the wire, there is no v1.5 decryption for an oracle to be &lt;em&gt;about&lt;/em&gt;. The deployed stacks already reflect this -- mainstream TLS libraries now default to TLS 1.3, and some, like rustls, never implemented the &lt;code&gt;TLS_RSA_*&lt;/code&gt; transport suites at all [@rustls-manual].&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; SSLv2, &lt;code&gt;RSA_EXPORT&lt;/code&gt;, and PKCS#1 v1.5 all stayed live in production for a decade or more after retirement, and every one was weaponized as a &lt;em&gt;live&lt;/em&gt; attack surface: DROWN, FREAK, ROBOT. &quot;Harmless legacy&quot; is a contradiction. Retiring RSA key transport is a migration, not a switch: v1.5 is still specified and ubiquitous in S/MIME, JWT and JOSE, hardware security modules, and legacy TLS, and the timing tail persists wherever v1.5 decryption survives [@marvin-paper].&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&quot;Validate generation, use OAEP and PSS, drop RSA key transport&quot; is the whole answer for greenfield code. But engineers inherit constraints -- a device that must self-generate keys, a peer that only speaks v1.5, a fleet already in the field -- so the real question is not &quot;what is best&quot; but &quot;what are my options at each layer, ranked, and exactly when does each apply?&quot;&lt;/p&gt;
&lt;h2&gt;7. Competing Approaches: How the Field Closes Each Gap&lt;/h2&gt;
&lt;p&gt;For each of the three loci, the honest picture is competing options with real trade-offs, not a single winner.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Pillar&lt;/th&gt;
&lt;th&gt;Option A&lt;/th&gt;
&lt;th&gt;Option B&lt;/th&gt;
&lt;th&gt;Option C&lt;/th&gt;
&lt;th&gt;Main trade-off&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;Key generation&lt;/td&gt;
&lt;td&gt;Hardware RNG plus entropy at boot&lt;/td&gt;
&lt;td&gt;Derandomized key generation from a strong seed&lt;/td&gt;
&lt;td&gt;Post-hoc structure and shared-factor testing&lt;/td&gt;
&lt;td&gt;Prevention versus detection; they coexist&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Padding&lt;/td&gt;
&lt;td&gt;OAEP (safe only with one constant-time generic error)&lt;/td&gt;
&lt;td&gt;Hardened uniform-error v1.5 (&lt;code&gt;RFC 5246&lt;/code&gt; random premaster)&lt;/td&gt;
&lt;td&gt;Abandon RSA encryption for (EC)DHE&lt;/td&gt;
&lt;td&gt;Compatibility versus a clean break&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Downgrade&lt;/td&gt;
&lt;td&gt;TLS 1.3 downgrade protection and protocol retirement&lt;/td&gt;
&lt;td&gt;Config hardening (&lt;code&gt;TLS_FALLBACK_SCSV&lt;/code&gt;, disable export and SSLv2)&lt;/td&gt;
&lt;td&gt;--&lt;/td&gt;
&lt;td&gt;A new protocol versus patching the old one&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;Key generation.&lt;/strong&gt; Three approaches coexist. A hardware RNG with guaranteed entropy at boot prevents the Debian and shared-prime failures at the source. Derandomized key generation -- deriving the primes from a single strong seed -- removes the runtime-entropy dependency entirely, at the cost of trusting that seed. And post-hoc structure and shared-factor testing (batch-GCD scanners, the ROCA detector) catches what generation missed [@factorable-2012, @roca-crocs, @fips186-5]. Measurement is the backstop for generation: ROCA and Mining Your Ps and Qs were both &lt;em&gt;discovered&lt;/em&gt; by scanning the field, not by auditing source.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Padding.&lt;/strong&gt; Three options, chosen by how much legacy you must carry. Fix the scheme with OAEP -- but recall Manger, that it is only misuse-safe with one constant-time generic error [@manger-2001]. Or keep v1.5 for compatibility and harden the implementation toward a uniform error, the RFC 5246 random-premaster substitution -- a twenty-five-year losing battle across error text, protocol siblings, implementation quirks, and timing [@rfc5246]. Or abandon RSA encryption for forward-secret (EC)DHE. Different deployments sit under different compatibility constraints, so all three survive in the field [@rfc8017, @robotattack].&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Downgrade.&lt;/strong&gt; Two options. TLS 1.3, with built-in downgrade protection and outright protocol and algorithm retirement, is the durable answer. For stacks that cannot move yet, configuration hardening -- &lt;code&gt;TLS_FALLBACK_SCSV&lt;/code&gt;, disabling export ciphers and SSLv2 -- is the interim one [@rfc8446, @drown-2016].&lt;/p&gt;
&lt;p&gt;Notice the convergence. The durable answer to both Pillar 2 and Pillar 3 is the &lt;em&gt;same&lt;/em&gt; move: do not do RSA key transport. The clean surviving split is that RSA-for-signatures (PSS) stays first-class while RSA-for-encryption is retired.&lt;/p&gt;
&lt;p&gt;Every one of these options changes how RSA is generated, used, or negotiated, or drops RSA key transport entirely, and each buys its safety with a specific cost -- a scan, a migration, a compatibility break, a forward-secret handshake. Which means the honest way to close the technical arc is to ask what is &lt;em&gt;provably&lt;/em&gt; true on both sides: how little the attacker needs, and how much the defender can actually guarantee.&lt;/p&gt;
&lt;h2&gt;8. What Is Provably True on Both Sides&lt;/h2&gt;
&lt;p&gt;The thesis has two sides, and each has its own frontier. The surprise is the asymmetry: the &lt;em&gt;math&lt;/em&gt; side is a slow, honest, well-mapped concession, and all the operationally relevant limits live in the &lt;em&gt;deployment&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The math side, the honest concession.&lt;/strong&gt; Factoring is real but slow. The Number Field Sieve runs in heuristic sub-exponential time $L_N[1/3, (64/9)^{1/3}]$, with $(64/9)^{1/3} \approx 1.923$ [@rsa240-2019], and the public record has crept forward for two decades.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Challenge&lt;/th&gt;
&lt;th&gt;Bits&lt;/th&gt;
&lt;th&gt;Year&lt;/th&gt;
&lt;th&gt;Effort&lt;/th&gt;
&lt;th&gt;Method&lt;/th&gt;
&lt;th&gt;Deployed strong key factored?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;RSA-155&lt;/td&gt;
&lt;td&gt;512&lt;/td&gt;
&lt;td&gt;1999&lt;/td&gt;
&lt;td&gt;feasible in hours today&lt;/td&gt;
&lt;td&gt;NFS&lt;/td&gt;
&lt;td&gt;Never&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RSA-768&lt;/td&gt;
&lt;td&gt;768&lt;/td&gt;
&lt;td&gt;2009&lt;/td&gt;
&lt;td&gt;~1500 core-years sieving&lt;/td&gt;
&lt;td&gt;NFS&lt;/td&gt;
&lt;td&gt;Never&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RSA-240&lt;/td&gt;
&lt;td&gt;795&lt;/td&gt;
&lt;td&gt;2019&lt;/td&gt;
&lt;td&gt;~900 core-years&lt;/td&gt;
&lt;td&gt;NFS&lt;/td&gt;
&lt;td&gt;Never&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RSA-250&lt;/td&gt;
&lt;td&gt;829&lt;/td&gt;
&lt;td&gt;2020&lt;/td&gt;
&lt;td&gt;~2700 core-years&lt;/td&gt;
&lt;td&gt;GNFS (CADO-NFS)&lt;/td&gt;
&lt;td&gt;Never&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RSA-2048&lt;/td&gt;
&lt;td&gt;2048&lt;/td&gt;
&lt;td&gt;--&lt;/td&gt;
&lt;td&gt;astronomically out of reach classically&lt;/td&gt;
&lt;td&gt;--&lt;/td&gt;
&lt;td&gt;Never&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Every row is a &lt;em&gt;challenge&lt;/em&gt; number from the old RSA Factoring Challenge, a public target list RSA Laboratories once published [@wiki-rsa-challenge], and none was ever a deployed key [@rsa155-2000, @rsa768-epfl, @rsa768-eprint, @rsa240-2019, @rsa250-2020]. 512-bit is hours, which is exactly why FREAK works; 2048-bit is out of classical reach; and Shor&apos;s algorithm collapses all of it on a quantum computer -- a story the companion &lt;em&gt;How RSA Would Break&lt;/em&gt; owns. Read the rightmost column top to bottom: it says &lt;em&gt;Never&lt;/em&gt; at every row, because no strong, correctly generated, deployed modulus has ever been factored in the field.RSA-250&apos;s roughly 2700 core-years split into about 2450 for sieving and 250 for the matrix step, run on Intel Xeon Gold 6130 cores with CADO-NFS. It is a 250-digit, 829-bit challenge number, and it never protected a deployed system [@rsa250-2020].&lt;/p&gt;
&lt;p&gt;The Heninger through-line closes here. The researcher who recovered weak keys by the thousand in Pillar 1 co-holds &lt;em&gt;this&lt;/em&gt; record -- the slow, honest way a &lt;em&gt;strong&lt;/em&gt; key falls [@rsa250-2020]. That is the entire weak-versus-strong distinction compressed into one career. And note the theory is not even settled in the defender&apos;s favor: there is no proof that factoring requires super-polynomial time, and the RSA-problem-versus-factoring equivalence is itself open [@boneh-1999]. Two-plus decades of open cryptanalysis is an empirical floor, not a theorem -- and it never once mattered in the field.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The deployment side, where the real limits live.&lt;/strong&gt; Here the limits are provable-in-practice, and none is about a strong modulus&apos;s strength.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Attack&lt;/th&gt;
&lt;th&gt;Attacked layer&lt;/th&gt;
&lt;th&gt;Worst-case work&lt;/th&gt;
&lt;th&gt;Needs a weak modulus?&lt;/th&gt;
&lt;th&gt;Recovers the private key?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;Padding oracle (Bleichenbacher family)&lt;/td&gt;
&lt;td&gt;Padding&lt;/td&gt;
&lt;td&gt;About $10^6$ queries, polynomial&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Batch-GCD shared primes&lt;/td&gt;
&lt;td&gt;Key generation&lt;/td&gt;
&lt;td&gt;One gcd, quasi-linear over the fleet&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ROCA and Coppersmith&lt;/td&gt;
&lt;td&gt;Key generation&lt;/td&gt;
&lt;td&gt;Lattice reduction, polynomial&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FREAK downgrade then factor&lt;/td&gt;
&lt;td&gt;Negotiation&lt;/td&gt;
&lt;td&gt;Hours to factor 512-bit&lt;/td&gt;
&lt;td&gt;Yes (forced)&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GNFS on a strong modulus&lt;/td&gt;
&lt;td&gt;The math itself&lt;/td&gt;
&lt;td&gt;Sub-exponential, ~2700 core-years at 829-bit&lt;/td&gt;
&lt;td&gt;--&lt;/td&gt;
&lt;td&gt;Only the challenge key&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Three boundaries, stated outright. First, a padding oracle needs no factoring: its query count is bounded below only by the roughly one-bit-per-conforming-reply information rate, so decryption is &lt;em&gt;many&lt;/em&gt; queries but always polynomial, and you cannot make an observable-validity scheme require exponentially many queries [@bleichenbacher-1998, @manger-2001]. Second, a shared prime is a single gcd away and a structured prime a Coppersmith lattice away -- quasi-linear or polynomial, regardless of key size [@factorable-2012, @roca-crocs]. Third, certification is not structural testing -- the ROCA lesson from Pillar 1, where FIPS 140-2 and CC EAL5+ chips shipped structurally factorable keys for years [@roca-crocs].&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key idea:&lt;/strong&gt; The math side is a slow, mapped concession -- an 829-bit public ceiling, and no strong deployed key ever factored. The deployment side holds the &lt;em&gt;real&lt;/em&gt; limits: a padding oracle needs no factoring, a shared prime is one gcd, a structured prime one lattice, a downgrade one forced 512-bit key -- all independent of key size. &quot;Strong-modulus-secure&quot; and &quot;system-secure&quot; are different claims, and the gap between them is inherent, not accidental.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;If the math side is a slow, mapped concession and the deployment side has hard, polynomial-cost limits, the practical frontier is obvious: it is wherever those deployment limits are still being hit in the wild. That is not a solved problem. It is an active one.&lt;/p&gt;
&lt;h2&gt;9. Where RSA Still Breaks in the Field&lt;/h2&gt;
&lt;p&gt;The math side is a slow, honest concession. The deployment side is not closed. Here are the places the same three-pillar pattern is still live, each an operational frontier, each consistent with the thesis that the weak link is a layer the math depends on, never the factoring problem.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Entropy on embedded, first-boot, and cloned systems (Pillar 1).&lt;/strong&gt; The exact Debian and shared-prime gap, revived by cloud VM cloning and container images that reproduce identical state at key-generation time [@ristenpart-yilek-2010]. The best partial answer is FIPS 186-5 generation plus fleet batch-GCD, but coverage of cloned and containerized keys is incomplete [@factorable-2012, @fips186-5].&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The PKCS#1 v1.5 long tail and timing oracles (Pillar 2).&lt;/strong&gt; v1.5 is still specified and everywhere. Marvin (2023) showed the &lt;em&gt;timing&lt;/em&gt; axis is still open across many libraries, tied to the working impossibility result that an observable-validity scheme cannot be patched uniformly safe, only structurally replaced [@marvin-paper, @marvin-iacr-news, @robotattack].&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Protocol and algorithm-retirement lag (Pillar 3).&lt;/strong&gt; How does a dead algorithm -- SSLv2, export RSA, v1.5 -- stay live for a decade or more? DROWN fell from 33% in 2016 to about 1.2% by 2019, and FREAK from 36.7% toward the low single digits: large but incomplete progress [@drown-2016, @freakattack]. This is a socio-technical problem, not a cryptanalytic one.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The next ROCA-class library (Pillar 1).&lt;/strong&gt; Detecting a &lt;em&gt;structurally&lt;/em&gt; biased generator inside certified closed hardware, before it ships millions of keys. The ROCA detector is signature-based, so a genuinely new structure would evade it [@roca-crocs].&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Post-quantum signature migration ahead of Shor.&lt;/strong&gt; This is the one future break that &lt;em&gt;is&lt;/em&gt; the math. Key exchange is already forward-secret, but signatures need proactive migration; the mechanics belong to the post-quantum and would-break siblings.&lt;/p&gt;
&lt;p&gt;One boundary is worth fencing so it is never miscounted as a field incident.Wiener&apos;s small-$d$ attack recovers a too-small private exponent $d &amp;lt; \tfrac{1}{3} n^{1/4}$ from the public key by continued fractions (1990) [@wiener-1990], and Hastad&apos;s low-exponent broadcast recovers the same unpadded low-$e$ message sent to $e$ recipients via CRT and an integer $e$-th root (1988) [@hastad-1988]. Both are real, but they are parameter-choice, theoretical breaks -- the canonical argument for randomized padding -- not documented field incidents. They belong to &lt;em&gt;How RSA Would Break&lt;/em&gt;. Parameter-choice attacks on deliberately bad exponents are a different genre from the deployment failures cataloged here.&lt;/p&gt;
&lt;p&gt;Every open problem here is the same sentence in new clothes: a contract the RSA math depends on is hard to keep in the real world. Which means the practical guide writes itself. It is the thesis made operational, each rule routed to the incident it prevents.&lt;/p&gt;
&lt;h2&gt;10. What to Do on Monday&lt;/h2&gt;
&lt;p&gt;Everything above collapses into a short decision procedure and a shorter list of nevers, each rule tied to the incident it prevents.&lt;/p&gt;

flowchart TD
    E{&quot;Real entropy before keygen?&quot;} --&amp;gt;|No| E1[&quot;Stop. Seed first. Never generate at unseeded boot.&quot;]
    E --&amp;gt;|Yes| K[&quot;FIPS 186-5 generation, 2048-bit or larger&quot;]
    K --&amp;gt; SCAN{&quot;Batch-GCD and ROCA detector&quot;}
    SCAN --&amp;gt;|Flagged| REV[&quot;Regenerate and revoke&quot;]
    SCAN --&amp;gt;|Clean| ENC{&quot;Need RSA encryption?&quot;}
    ENC --&amp;gt;|Yes| OAEP[&quot;OAEP, one constant-time generic error&quot;]
    ENC --&amp;gt;|No| DHE[&quot;Prefer (EC)DHE, TLS 1.3&quot;]
    OAEP --&amp;gt; SIG[&quot;Signatures: RSA-PSS, plan PQC&quot;]
    DHE --&amp;gt; SIG
    SIG --&amp;gt; NEG[&quot;Disable SSLv2, export, and RSA_EXPORT, add downgrade protection&quot;]
&lt;p&gt;&lt;strong&gt;Key generation.&lt;/strong&gt; Guarantee real entropy before the first key is generated; follow FIPS 186-5; never generate keys at unseeded first boot; use a 2048-bit minimum with validated generation; and run batch-GCD and the ROCA detector across your keys and CT logs, regenerating or revoking anything flagged. This prevents Debian enumeration, shared primes, and ROCA [@fips186-5, @factorable-2012, @roca-crocs].&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Padding and encryption.&lt;/strong&gt; Never use raw PKCS#1 v1.5. If you must do RSA encryption, use OAEP with constant-time, uniform-error handling -- one generic error, per Manger; better still, prefer (EC)DHE forward secrecy and drop RSA key transport, which TLS 1.3 does for you. This prevents Bleichenbacher, DROWN, ROBOT, and Marvin [@rfc8017, @manger-2001, @rfc8446].&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Signatures.&lt;/strong&gt; Use RSA-PSS, which is not what the oracles break, and plan the post-quantum signature migration ahead of Shor [@rfc8017].&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Negotiation.&lt;/strong&gt; Disable SSLv2, export ciphers, and &lt;code&gt;RSA_EXPORT&lt;/code&gt;; enable downgrade protection; prefer TLS 1.3 over 1.2. This prevents FREAK and DROWN&apos;s SSLv2 channel [@rfc8446, @drown-2016].&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Do this...&lt;/th&gt;
&lt;th&gt;...and you reproduce&lt;/th&gt;
&lt;th&gt;The fix&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;Generate keys at unseeded first boot&lt;/td&gt;
&lt;td&gt;Debian enumeration, shared primes&lt;/td&gt;
&lt;td&gt;Real entropy before keygen; batch-GCD&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Trust a certified black box as structurally safe&lt;/td&gt;
&lt;td&gt;ROCA&lt;/td&gt;
&lt;td&gt;ROCA detector; regenerate&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reveal padding validity by error, reset, timeout, or timing&lt;/td&gt;
&lt;td&gt;Bleichenbacher, DROWN, ROBOT, Marvin&lt;/td&gt;
&lt;td&gt;Uniform constant-time errors; OAEP; drop RSA transport&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reuse one RSA key across protocols&lt;/td&gt;
&lt;td&gt;DROWN, via SSLv2&lt;/td&gt;
&lt;td&gt;Separate keys; disable SSLv2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Leave export ciphers or SSLv2 live&lt;/td&gt;
&lt;td&gt;FREAK, DROWN&lt;/td&gt;
&lt;td&gt;Remove export and SSLv2; downgrade protection&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Assume &quot;2048-bit means safe&quot;&lt;/td&gt;
&lt;td&gt;Every non-factoring break&lt;/td&gt;
&lt;td&gt;Secure all three layers, not the key size&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;The batch-GCD check is not just an attacker&apos;s tool; it is your fleet audit. Run it against your own moduli:&lt;/p&gt;
&lt;p&gt;{&lt;code&gt;const gcd = (a, b) =&amp;gt; { while (b) { [a, b] = [b, a % b]; } return a; }; // Public moduli scraped from your own devices or CT logs. const fleet = {   &apos;host-a&apos;: 63900000000000000000000000000000000000000000002625100000000000000000000000000000000000000000026931n,   &apos;host-b&apos;: 74700000000000000000000000000000000000000000003196300000000000000000000000000000000000000000034189n,   &apos;host-c&apos;: 35510000000000000000000000000000000000000000001968800000000000000000000000000000000000000000010257n,   &apos;host-d&apos;: 18130000000000000000000000000000000000000000001447800000000000000000000000000000000000000000026649n, }; const names = Object.keys(fleet); let flagged = 0; for (let i = 0; i &amp;lt; names.length; i++) {   for (let j = i + 1; j &amp;lt; names.length; j++) {     const g = gcd(fleet[names[i]], fleet[names[j]]);     if (g &amp;gt; 1n) {       flagged++;       console.log(&apos;WEAK PAIR:&apos;, names[i], &apos;&amp;amp;&apos;, names[j], &apos;-&amp;gt; shared factor&apos;, g.toString());     }   } } console.log(flagged ? (&apos;Regenerate and revoke &apos; + flagged + &apos; compromised pair(s).&apos;) : &apos;No shared factors found.&apos;);&lt;/code&gt;}&lt;/p&gt;

To feed real keys in, extract each modulus first with a one-liner like `openssl x509 -in cert.pem -noout -modulus`, collect them into the list above, and run it. Any pair with a gcd greater than 1 is a compromised pair: regenerate and revoke both, then find out why two devices drew the same prime.
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; One: validated high-entropy, structure-checked key generation (FIPS 186-5) plus fleet batch-GCD and ROCA scans. Two: OAEP for encryption and PSS for signatures with uniform, constant-time errors -- or, better, drop RSA key transport for (EC)DHE. Three: TLS 1.3, with SSLv2, export ciphers, and &lt;code&gt;RSA_EXPORT&lt;/code&gt; disabled.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The checklist is short because the lesson is one sentence. Before restating it, let us clear the handful of confident, wrong sentences that keep these bugs alive in design meetings.&lt;/p&gt;
&lt;h2&gt;11. Misconceptions, Precisely Corrected&lt;/h2&gt;
&lt;p&gt;The paradox survives mostly because a few plausible, wrong statements keep getting repeated. Here they are, corrected, each answer tied back to a catalog entry and the three-pillar thesis.&lt;/p&gt;


Only *weak* keys: 512-bit export keys (FREAK), shared-prime keys, and ROCA-structured keys. The public *challenge* ceiling is the 829-bit RSA-250, which took about 2,700 core-years in 2020. No strong, correctly generated, deployed modulus has ever been factored [@rsa250-2020, @freakattack].


No. It is a decryption or signing *oracle*: the attacker decrypts one session or forges one signature, but the key is never extracted and the modulus is never factored [@robotattack, @bleichenbacher-1998].


No -- it is a different scheme. The padding oracle is on v1.5 *encryption*. PSS signatures, and even v1.5 signatures, are not what falls. Keep the two constructions distinct [@rfc8017].


No. RSA signing has no secret per-signature nonce. The per-signature $k$-reuse break behind the PlayStation 3 and Android incidents is an (EC)DSA phenomenon, told in its own article [@ps3-epic-fail-2010, @bitcoin-android-alert-2013].


Not for padding oracles, shared or structured primes, or downgrade -- those are deployment defects, independent of key size. Size only matters against brute factoring, which is exactly why 512-bit export was fatal and a strong key is not [@factorable-2012, @roca-crocs].


Not necessarily. ROCA&apos;s keys were CC EAL5+ certified; certification tested the RNG, not the public-key *structure*, and roughly 760,000 factorable keys shipped anyway [@roca-crocs].


The primitive, no. But stop using RSA key transport and raw PKCS#1 v1.5, prefer forward-secret (EC)DHE and OAEP or PSS, and plan the post-quantum signature migration ahead of Shor [@rfc8446, @rfc8017].

&lt;p&gt;Every correction points at the same root: the factoring problem was never the weak link. Time to say the sentence the whole article was built to earn.&lt;/p&gt;
&lt;h2&gt;To Break RSA in the Field, You Never Factor a Strong Modulus&lt;/h2&gt;
&lt;p&gt;Return to the opening paradox, now resolved. The public factoring ceiling is an 829-bit challenge number that cost about 2,700 core-years, and no strong deployed key has ever fallen to it [@rsa250-2020]. And yet national ID cards were forgeable, a third of HTTPS was decryptable, embedded keys were recoverable, and sessions were man-in-the-middled -- because every one of those breaks happened at a layer the factoring problem knows nothing about.&lt;/p&gt;
&lt;p&gt;The primes were weak before the math began: Debian, shared primes, ROCA. The padding check was an oracle: Bleichenbacher, DROWN, ROBOT. The negotiation forced a crippled key: FREAK. Three layers, seven deployed field breaks, and the factoring problem on a strong modulus untouched in all of them. RSA-250, the eighth row of the catalog, is the &lt;em&gt;ceiling&lt;/em&gt; -- a strong-but-undeployed challenge number, not a field break.&lt;/p&gt;
&lt;p&gt;Every fix changed how RSA is generated, used, or negotiated -- validated entropy and structure tests, OAEP and PSS and uniform errors, dead export and SSLv2 and TLS 1.3 -- never RSA. And where three incidents did end in a factorization, the deployment had already produced a &lt;em&gt;weak&lt;/em&gt; modulus, so the math merely finished a job it had been handed.&lt;/p&gt;

When your RSA keys fall, don&apos;t ask whether someone factored the modulus. Ask which layer the math depends on but does not control gave way: the primes, the padding, or the negotiation.
&lt;p&gt;One last time, keep the hedge honest: almost never, not never. Factoring is real and fell RSA-512, RSA-768, and RSA-250 as challenge numbers, and Shor&apos;s algorithm is coming for strong keys on a quantum computer -- but that is the story &lt;em&gt;How RSA Would Break&lt;/em&gt; tells, and the constructive OAEP and PSS depth belongs to &lt;em&gt;RSA Done Right&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;The factoring problem was never the weak link in the field, and no bigger key would have saved a single one of these deployments. That is why this is Part 3 of a series about how things break &lt;em&gt;in real life&lt;/em&gt;, not a chapter on the Number Field Sieve.&lt;/p&gt;
&lt;p&gt;&amp;lt;StudyGuide slug=&quot;how-rsa-breaks-in-real-life&quot; keyTerms={[
  { term: &quot;RSA trapdoor permutation&quot;, definition: &quot;n = p times q with public (n, e) and private d; factoring is sufficient to break RSA and remains the best known attack, while whether inverting RSA is as hard as factoring is an open question.&quot; },
  { term: &quot;Textbook (raw) RSA and malleability&quot;, definition: &quot;Unpadded RSA is deterministic and malleable, so multiplying a ciphertext by s to the e multiplies the plaintext by s; it must be padded before use.&quot; },
  { term: &quot;Entropy and CSPRNG seeding&quot;, definition: &quot;A generator&apos;s output is only as unpredictable as its seed; a starved, crippled, or biased seed yields predictable, enumerable, or colliding primes.&quot; },
  { term: &quot;Shared-factor (batch-GCD) recovery&quot;, definition: &quot;If two moduli share a prime, one gcd factors both; a batch-GCD computes every pairwise gcd across millions of keys at once.&quot; },
  { term: &quot;PKCS#1 v1.5 padding&quot;, definition: &quot;The 00 02 encryption format wrapped around a message; its validity check is what becomes Bleichenbacher&apos;s oracle.&quot; },
  { term: &quot;Padding oracle&quot;, definition: &quot;A decryptor that reveals whether padding was valid leaks one bit per query; enough adaptive queries decrypt a message without the private key.&quot; },
  { term: &quot;Coppersmith&apos;s method&quot;, definition: &quot;Finds small roots of a polynomial modulo N in polynomial time; when primes are structured it factors n far faster than general factoring, the engine behind ROCA.&quot; },
  { term: &quot;Export-grade RSA&quot;, definition: &quot;Deliberately weak 512-bit RSA_EXPORT key exchange and the obsolete SSLv2 protocol, retired in principle but live long enough to power FREAK and DROWN.&quot; },
  { term: &quot;RSA-OAEP and RSA-PSS&quot;, definition: &quot;The modern encryption and signature paddings; OAEP removes the Bleichenbacher oracle only when decryption returns one constant-time generic error, per Manger.&quot; }
]} /&amp;gt;&lt;/p&gt;
</content:encoded><category>cryptography</category><category>rsa</category><category>roca</category><category>bleichenbacher</category><category>padding-oracle</category><category>freak</category><category>tls</category><category>key-generation</category><author>noreply@paragmali.com (Parag Mali)</author></item><item><title>RSA Is a Trapdoor, Not a Cryptosystem: OAEP, PSS, and the 25-Year Padding-Oracle Lineage</title><link>https://paragmali.com/blog/rsa-is-a-trapdoor-not-a-cryptosystem-oaep-pss-and-the-25-yea/</link><guid isPermaLink="true">https://paragmali.com/blog/rsa-is-a-trapdoor-not-a-cryptosystem-oaep-pss-and-the-25-yea/</guid><description>Textbook RSA is a trapdoor, not a cryptosystem. A field guide to OAEP, PSS, PKCS#1 v1.5, and the Bleichenbacher-ROBOT-Marvin padding-oracle lineage, done right.</description><pubDate>Sat, 11 Jul 2026 09:07:03 GMT</pubDate><content:encoded>
**RSA is a trapdoor permutation, not a cryptosystem** -- and almost every real-world RSA break came from treating the bare trapdoor as if it were already secure. Textbook RSA is deterministic and malleable. PKCS#1 v1.5 *encryption* turns &quot;is this padding valid?&quot; into a decryption oracle that leaked through ever-quieter channels for 25 years, from Bleichenbacher (1998) [@bleichenbacher98] through DROWN [@drown16] and ROBOT [@robot18] to Marvin&apos;s pure-timing attack (2023) [@marvin23]. &quot;Done right&quot; is a stack that must all hold at once: the right scheme (**OAEP** for encryption, **PSS** for new signatures), the right parameters (2048-bit floor, `e = 65537`, CSPRNG primes), and a **constant-time, fault-checked implementation** -- because security is a property of the scheme *and* its implementation, not of the math. And keep one split absolute: v1.5 *signatures* are unbroken and still dominant (verify strictly), while v1.5 *encryption* must be retired.
&lt;h2&gt;1. The Modulus Was Never Factored&lt;/h2&gt;
&lt;p&gt;In 2018, three researchers signed a message with the private key behind &lt;code&gt;facebook.com&lt;/code&gt;&apos;s TLS certificate [@robot18]. They never factored Facebook&apos;s 2,048-bit modulus. Nobody has ever publicly factored a 2,048-bit modulus -- the public record stands at 829 bits, and that took roughly 2,700 core-years [@rsa250].&lt;/p&gt;
&lt;p&gt;Instead, they asked one of Facebook&apos;s front-end servers the same yes-or-no question a few hundred thousand times -- &lt;em&gt;is this padding valid?&lt;/em&gt; -- and let the pattern of answers spell out the secret [@robot18]. The attack they used was already nineteen years old [@robotsite]. Five years later, a Red Hat engineer reproduced the same break against software that was supposed to be immune, using nothing but a stopwatch, and called it Marvin [@marvin23].&lt;/p&gt;
&lt;p&gt;Notice what did not happen. No prime was recovered. No number was factored. The RSA problem -- invert &lt;code&gt;c = m^e mod N&lt;/code&gt; without the private key -- stood exactly as hard the day after as the day before. What broke was the server&apos;s &lt;em&gt;reaction&lt;/em&gt;: the way it answered a question about a ciphertext it did not create.&lt;/p&gt;
&lt;p&gt;That is the whole subject in one sentence. Textbook RSA -- the bare &lt;code&gt;m^e mod N&lt;/code&gt; you meet in a first course -- is a &lt;strong&gt;trapdoor permutation, not a cryptosystem&lt;/strong&gt;. It is a beautiful one-way function with a secret shortcut, and nothing more.&lt;/p&gt;
&lt;p&gt;Everything that turns it into secure encryption or a secure signature lives &lt;em&gt;around&lt;/em&gt; the permutation, in the padding, the parameters, and the code that runs the operation. Almost every famous RSA disaster of the last three decades is a variation on one theme: someone used the bare trapdoor as if it were already a cryptosystem, or let the machinery meant to secure it confess -- through an error message, a network timeout, a microsecond of timing, or an injected fault -- whether a check had passed.&lt;/p&gt;

The math was never broken. The padding check told the attacker whether it passed.
&lt;p&gt;So here is the diagnostic question this article keeps asking, the one that unlocks the entire failure catalog: &lt;strong&gt;when a ciphertext or signature it did not create arrives, what does the receiver reveal about it -- through its answer, its timing, or its faults -- before and after it has checked the padding?&lt;/strong&gt; Four names you may know as headlines -- Bleichenbacher, DROWN, ROBOT, Marvin -- are four answers to that question, each leaking the same single bit through a quieter channel than the last. To see why one yes-or-no question about padding is enough to reconstruct a session key, you first have to see what RSA actually &lt;em&gt;is&lt;/em&gt;, and what it is not.&lt;/p&gt;
&lt;h2&gt;2. A Trapdoor for Diffie-Hellman&apos;s Challenge&lt;/h2&gt;
&lt;p&gt;In 1976, Whitfield Diffie and Martin Hellman wrote down the shape of a future that did not yet have an engine. Their paper &lt;em&gt;New Directions in Cryptography&lt;/em&gt; described public-key encryption and, remarkably, the idea of a digital signature -- a value only you can produce but anyone can check [@dh76]. What they could not supply was a concrete &lt;em&gt;trapdoor&lt;/em&gt;: a function easy to compute in one direction, hard to invert, yet effortless to invert if you hold a secret. Their paper is a challenge with a hole in it, and the hole is shaped exactly like RSA.&lt;/p&gt;
&lt;p&gt;A year later, Ron Rivest, Adi Shamir, and Leonard Adleman filled it. Pick two large primes $p$ and $q$ and set $N = pq$. Choose a public exponent $e$, and compute a private exponent $d$ with $ed \equiv 1 \pmod{\lambda(N)}$. Publish $(N, e)$; keep $d$, $p$, and $q$ secret. To encrypt a message represented as a number $m &amp;lt; N$, raise it to the public exponent; to decrypt, raise the result to the private one [@rsa78]:&lt;/p&gt;
&lt;p&gt;$$c = m^e \bmod N, \qquad m = c^d \bmod N.$$&lt;/p&gt;
&lt;p&gt;Why does the second operation undo the first? Because of Euler&apos;s theorem (1763): for any $m$ coprime to $N$, $m^{\phi(N)} \equiv 1 \pmod N$, with the totient $\phi(N) = (p-1)(q-1)$. Its Carmichael-function refinement (Carmichael, 1910) gives the same identity for the smaller $\lambda(N) = \mathrm{lcm}(p-1, q-1)$. Since $ed \equiv 1 \pmod{\lambda(N)}$, we have $ed = 1 + k\lambda(N)$ for some integer $k$, so $c^d = m^{ed} = m \cdot (m^{\lambda(N)})^k \equiv m \pmod N$. The exponents cancel, and the plaintext falls out -- but only for someone who could compute $d$, and computing $d$ requires $\lambda(N)$, which requires the factors of $N$. That is the trapdoor: the factorization of $N$ is the secret that inverts the permutation.The original 1978 paper used $\phi(N) = (p-1)(q-1)$, Euler&apos;s totient. Modern standards use the smaller Carmichael function $\lambda(N) = \mathrm{lcm}(p-1, q-1)$, which yields a smaller valid $d$ and the same correctness. Either works; $\lambda(N)$ is now the convention in FIPS 186-5.&lt;/p&gt;

A function that is easy to evaluate in the forward direction, computationally hard to invert without a secret, and easy to invert with it. RSA&apos;s forward map is x maps to x raised to the e, modulo N; the trapdoor that inverts it is the factorization of N. A trapdoor permutation is a primitive, not a complete encryption scheme -- it says nothing about hiding partial information or resisting tampering.
&lt;p&gt;The elegance that made RSA famous is that the &lt;em&gt;same&lt;/em&gt; operation both encrypts and signs. Swap the roles of the exponents: to sign a message, raise it to the private exponent, $s = m^d \bmod N$, an act only the key holder can perform; to verify, raise the signature to the public exponent and check that $s^e \equiv m \pmod N$, which anyone can do [@rsa78]. One modular exponentiation, read in two directions, is both a lock only you can open and a seal only you can stamp. Boneh&apos;s survey calls this duality the source of both RSA&apos;s reach and its long catalogue of misuse [@boneh99].&lt;/p&gt;
&lt;p&gt;Two engineering choices from this era matter later, because each returns as an attack surface. The first is the public exponent. Almost every deployed RSA key uses $e = 65537$.65537 is the Fermat prime $F_4 = 2^{16} + 1$. Written in binary it is &lt;code&gt;1&lt;/code&gt; followed by fifteen &lt;code&gt;0&lt;/code&gt;s and a final &lt;code&gt;1&lt;/code&gt; -- a Hamming weight of just 2 -- so public-key operations cost only sixteen squarings and one multiplication. Small enough to be fast, large enough to dodge the low-exponent traps of $e = 3$. The second is how the private operation is computed. Rather than one exponentiation modulo $N$, implementations work modulo $p$ and modulo $q$ separately and recombine, using the Chinese Remainder Theorem for roughly a fourfold speedup.&lt;/p&gt;

A technique for computing the private operation c raised to the d, modulo N, by working separately modulo p and modulo q on half-size numbers and then recombining the two halves. It cuts the cost of decryption and signing by about four times. Because it splits the secret operation across the two prime factors, it also opens a physical-fault attack surface that the single-modulus form does not have.
&lt;p&gt;Hold those two choices in mind: the small public exponent and the CRT shortcut will each come back to bite an implementation that treats them casually. But the deepest seed was planted in the 1978 paper itself, and it was invisible at the time.&lt;/p&gt;
&lt;p&gt;Rivest, Shamir, and Adleman demonstrated the trapdoor on &lt;em&gt;raw&lt;/em&gt; message numbers. That bare application is deterministic, algebraically malleable, and carries structure an attacker can exploit -- three properties that, in 1978, looked like harmless features of a clean mathematical object. One operation, elegant enough to both lock and sign. So why is calling that operation directly -- what cryptographers now dismiss as &quot;textbook RSA&quot; -- treated as a bug in every serious codebase?&lt;/p&gt;
&lt;h2&gt;3. Why Textbook RSA Is Not a Cryptosystem&lt;/h2&gt;
&lt;p&gt;Encryption has a minimum bar -- lower than most people think -- and textbook RSA fails to clear it. The bar is called &lt;a href=&quot;https://paragmali.com/blog/secure-against-whom-the-security-definitions-every-protocol-/&quot; rel=&quot;noopener&quot;&gt;IND-CPA&lt;/a&gt;: an adversary who picks two plaintexts and receives the encryption of one cannot tell which, better than a coin flip. (Part 1 of this series builds that definition carefully; here we only need its consequences.) Bare RSA loses this game three separate ways, each a direct consequence of the naked permutation.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;It is deterministic.&lt;/strong&gt; The same plaintext always encrypts to the same ciphertext, because $m^e \bmod N$ is a function with no randomness in it. That is fatal whenever the message space is small or guessable. Suppose a server encrypts a one-word trading instruction, either &lt;code&gt;BUY&lt;/code&gt; or &lt;code&gt;SELL&lt;/code&gt;, under a known public key. An eavesdropper does not need to decrypt anything: they encrypt both candidate words, compare against the captured ciphertext, and read the plaintext with zero queries. Determinism can never be semantic security -- this is structural, not a missing optimization you can bolt on later.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;It is malleable.&lt;/strong&gt; RSA is multiplicatively homomorphic: multiply a ciphertext by $k^e$ and the plaintext is silently multiplied by $k$.&lt;/p&gt;

A cipher is malleable when an attacker can transform a ciphertext into another valid ciphertext whose plaintext is a predictable function of the original -- without decrypting anything. RSA satisfies the multiplicative relation: the encryption of m times the encryption-form of k decrypts to m times k, modulo N. Useful for some protocols, catastrophic for confidentiality, and the exact algebraic lever Bleichenbacher later pulls.
&lt;p&gt;That relation is not a curiosity; it is the crowbar behind the entire padding-oracle lineage. The demonstration below uses toy primes so the arithmetic is legible in a browser, but the algebra is identical at 2,048 bits.&lt;/p&gt;
&lt;p&gt;{`
// Toy RSA: p=61, q=53, N=3233, e=17, d=2753. NOT secure sizes -- for illustration.
function modpow(base, exp, mod) {
  base = base % mod; let r = 1n;
  while (exp &amp;gt; 0n) {
    if (exp &amp;amp; 1n) r = (r * base) % mod;
    base = (base * base) % mod; exp &amp;gt;&amp;gt;= 1n;
  }
  return r;
}
const N = 3233n, e = 17n, d = 2753n;
const m = 42n;&lt;/p&gt;
&lt;p&gt;// 1) Deterministic: encrypting the same message twice gives the same ciphertext.
const c1 = modpow(m, e, N), c2 = modpow(m, e, N);
console.log(&apos;encrypt 42 twice -&amp;gt;&apos;, c1.toString(), c2.toString(), &apos;| identical?&apos;, c1 === c2);&lt;/p&gt;
&lt;p&gt;// 2) Malleable: multiply the ciphertext by k^e and the plaintext scales by k.
const k = 2n;
const cForged = (c1 * modpow(k, e, N)) % N;      // attacker never decrypts
const mForged = modpow(cForged, d, N);           // what the victim would recover
console.log(&apos;tampered ct decrypts to&apos;, mForged.toString(), &apos;= (42*2) mod N =&apos;, ((m * k) % N).toString());
`}&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;It leaks structure for small exponents and short messages.&lt;/strong&gt; If a message is short enough that $m^e &amp;lt; N$, no modular reduction ever happens, so recovering $m$ is a plain integer $e$-th root -- no factoring required.&lt;/p&gt;
&lt;p&gt;Johan Hastad showed in 1988 that if the same message is broadcast to $e$ recipients under $e = 3$, the plaintext falls out by the Chinese Remainder Theorem [@hastad88]. Don Coppersmith sharpened this in 1996 into a lattice method that recovers a message whenever the unknown part is smaller than $N^{1/e}$, breaking &quot;stereotyped&quot; messages with a small hidden field [@coppersmith97]. Every one of these is an attack on &lt;em&gt;textbook&lt;/em&gt; RSA, not on the RSA problem itself -- the factoring assumption stays intact while the plaintext walks out the front door [@boneh99].&lt;/p&gt;
&lt;p&gt;So a trapdoor permutation is not a cryptosystem. To become one, it needs a transformation applied to the message &lt;em&gt;before&lt;/em&gt; the permutation -- padding -- and that padding has to do three separable jobs.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key idea:&lt;/strong&gt; A trapdoor becomes a cryptosystem only when the padding does three separable jobs: (1) add &lt;strong&gt;randomness&lt;/strong&gt;, so equal plaintexts produce different ciphertexts; (2) add &lt;strong&gt;checkable redundancy&lt;/strong&gt;, so the receiver can detect a tampered or malformed ciphertext; and (3) -- the job the field kept forgetting -- be &lt;strong&gt;implemented so that no observable behaviour reveals whether that check passed.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The first two jobs are about the &lt;em&gt;scheme&lt;/em&gt;. The third is about the &lt;em&gt;implementation&lt;/em&gt;, and the entire history of RSA breaks is the field learning, over and over, that the third job is not optional. Hold that third clause; it is the trap the next section springs.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; If your code invokes a modular-exponentiation &quot;textbook RSA&quot; function directly on a message -- no OAEP, no PSS, no padding layer -- it is broken before it ships. Every serious library hides the bare permutation precisely so that no application accidentally uses it as encryption or a signature.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The first widely deployed answer to those three jobs was PKCS#1 version 1.5, specified by RSA Laboratories in the early 1990s and republished as RFC 2313 in 1998 [@rfc2313]. For encryption, it wraps the message as &lt;code&gt;0x00 || 0x02 || PS || 0x00 || M&lt;/code&gt;, where &lt;code&gt;PS&lt;/code&gt; is at least eight nonzero random bytes -- that random padding supplies job one [@rfc8017]. For signatures it uses a different, fixed frame, &lt;code&gt;0x00 || 0x01 || 0xFF...0xFF || 0x00 || DigestInfo&lt;/code&gt;, whose rigid structure supplies job two [@rfc8017].&lt;/p&gt;
&lt;p&gt;On paper it fixed textbook RSA&apos;s two headline problems: the random bytes killed determinism, and the fixed structure gave the receiver something to check. For five years, it looked as if PKCS#1 v1.5 had turned the trapdoor into a cryptosystem. The structure was supposed to &lt;em&gt;add&lt;/em&gt; security. In 1998, Daniel Bleichenbacher showed that the structure was the vulnerability.&lt;/p&gt;
&lt;h2&gt;4. The Atom and Its Echoes: The Encryption Padding-Oracle Lineage&lt;/h2&gt;
&lt;p&gt;Before the detailed walk, here is the whole story on one timeline -- two intertwined tracks, the schemes on one side and the breaks on the other, with each break forcing the next response.&lt;/p&gt;

timeline
    title RSA schemes and their breaks, 1976 to 2026
    1977 : RSA trapdoor answers the Diffie-Hellman challenge
    1990 : Wiener breaks small private exponents
    1993 : PKCS#1 v1.5 padding deployed
    1997 : Bellcore CRT fault factors N from one signature
    1998 : Bleichenbacher padding oracle : OAEP standardized in response
    2001 : Manger reopens the oracle inside OAEP decoders
    2003 : Remote timing attacks proven practical : PSS standardized
    2016 : DROWN revives the oracle through SSLv2
    2017 : ROCA factors structured Infineon keys
    2018 : ROBOT signs with the facebook.com key : TLS 1.3 deletes RSA key exchange
    2023 : Marvin recovers keys by timing alone
    2024 : NIST IR 8547 sets the quantum retirement clock
    2025 : CFRG draft moves to deprecate v1.5 encryption
&lt;p&gt;Bleichenbacher&apos;s insight was not about RSA the mathematics at all. It was about a server&apos;s manners. A TLS server in the 1990s received an RSA-encrypted pre-master secret, decrypted it, and checked whether the result had valid PKCS#1 v1.5 padding -- did it start with the bytes &lt;code&gt;0x00 0x02&lt;/code&gt;, with a nonzero pad and a delimiter in the right place? If not, it returned an error. That error, however polite, answered a question the attacker was not supposed to be able to ask: &lt;em&gt;was this the encryption of a well-formed message?&lt;/em&gt; And because RSA is malleable, the attacker could turn that one bit of feedback into a full decryption.&lt;/p&gt;
&lt;p&gt;Here is the mechanism. The attacker holds a target ciphertext $c$ that encrypts an unknown $m$ -- say, a captured TLS session key. They pick a value $s$, compute $c \cdot s^e \bmod N$, and send it to the server. By the homomorphic property, the server is really decrypting $m \cdot s \bmod N$. If the server reports valid padding, the attacker learns that $m \cdot s \bmod N$ begins with &lt;code&gt;0x00 0x02&lt;/code&gt; -- which means it lies in the interval $[2B, 3B)$, where $B = 2^{8(k-2)}$ for a $k$-byte modulus.&lt;/p&gt;
&lt;p&gt;Each conforming $s$ is a linear inequality on the secret $m$. Collect enough of them, adaptively choosing each new $s$ to bisect the surviving range, and the interval of possible $m$ collapses to a single value. Bleichenbacher&apos;s 1998 paper showed this takes on the order of $2^{20}$ -- about a million -- queries for a 1,024-bit key, and the private key is never touched [@bleichenbacher98].&lt;/p&gt;

sequenceDiagram
    participant A as Attacker
    participant S as Decrypting server
    Note over A: Holds target ciphertext c, wants secret m
    A-&amp;gt;&amp;gt;S: Submit c times s to the e, for chosen s
    S-&amp;gt;&amp;gt;S: Decrypt and check PKCS#1 v1.5 padding
    alt Decrypted value starts with 00 02
        S--&amp;gt;&amp;gt;A: Conforming, no error or fast reply
        Note over A,S: Attacker learns m times s mod N is in 2B to 3B
    else Padding invalid
        S--&amp;gt;&amp;gt;A: Non-conforming, error, alert, or slow reply
    end
    Note over A: Narrow the possible range of m, choose next s
    A-&amp;gt;&amp;gt;S: Repeat, roughly a million queries in total
    Note over A: Range collapses to one value, plaintext m recovered
&lt;p&gt;A generation of engineers filed this under &quot;performance&quot; or &quot;obscure edge case.&quot; It is neither. It is a correctness and security failure of the &lt;em&gt;construction&lt;/em&gt;: the receiver&apos;s validity check, exposed through any observable side effect, is a decryption oracle for chosen ciphertexts. That is the &lt;a href=&quot;https://paragmali.com/blog/they-read-your-plaintext-without-breaking-your-cipher-a-fiel/&quot; rel=&quot;noopener&quot;&gt;symmetric-side padding-oracle attack&lt;/a&gt; of Part 6, transplanted to public-key land -- the same disease, a different organ.&lt;/p&gt;

Any observable behaviour -- an error message, a network reset, an injected fault, or a difference in response time -- that reveals whether a decrypted ciphertext had valid padding. Because the attacker chooses the ciphertexts, a padding-validity signal becomes a decryption oracle: enough yes-or-no answers reconstruct the plaintext without ever recovering the private key.
&lt;p&gt;The break violates the strongest standard security goal for encryption, IND-CCA2, in which the adversary may submit chosen ciphertexts to a decryption oracle and still must not learn anything about a challenge plaintext.&lt;/p&gt;

The security goal a padding oracle destroys: an adversary who can submit arbitrary ciphertexts for decryption still cannot distinguish which of two chosen plaintexts a challenge ciphertext encrypts. PKCS#1 v1.5 encryption fails this because its validity check leaks. Part 1 of this series develops the full definition.
&lt;p&gt;The scheme-level fix arrived almost immediately. In direct response to Bleichenbacher, RSA Laboratories standardized a new encryption padding, RSAES-OAEP, in PKCS#1 v2.0 (RFC 2437) later in 1998 [@rfc2437]. OAEP&apos;s design goal, spelled out in Section 6, is that any tampered ciphertext decodes to unstructured noise, so there is no &quot;conformant&quot; interval left to test -- the oracle has nothing to grade. It fixed the scheme. What it did not, and could not, fix by itself was the decoder.&lt;/p&gt;
&lt;p&gt;James Manger proved that in 2001. OAEP&apos;s decoding has two distinct early failure modes: the recovered integer can be too large to fit the byte block, or it can fit but fail the padding check. A decoder that distinguishes those two cases through different errors or different timing hands the attacker a &lt;em&gt;new&lt;/em&gt; oracle -- and Manger&apos;s variant is dramatically cheaper than Bleichenbacher&apos;s, needing on the order of $\log_2 N$ queries, roughly 2,048 for a 2,048-bit key, instead of $2^{20}$ [@manger01]. OAEP, the provably secure scheme, re-opened the identical class of attack through an imperfect implementation. The lesson the field wrote down, and then kept having to re-learn, was not &quot;use OAEP.&quot; It was &quot;use OAEP &lt;em&gt;and&lt;/em&gt; decode it in constant time.&quot;&lt;/p&gt;
&lt;p&gt;If Bleichenbacher&apos;s attack is the atom, the next quarter-century is three quieter echoes of it -- the same leaked bit, reached through channels that get progressively harder to see.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;DROWN (2016): a cross-protocol channel.&lt;/strong&gt; By 2016, TLS servers had long since patched the obvious padding-error message. But many still supported SSLv2, an obsolete protocol from the 1990s, often on a different service such as a mail server -- and often with the &lt;em&gt;same&lt;/em&gt; RSA key. DROWN showed that an attacker could use the weak, deliberately export-crippled SSLv2 endpoint as the padding oracle, then use its answers to decrypt a modern TLS session that shared the key.The DROWN team measured that roughly 33 percent of all HTTPS servers were vulnerable at disclosure in March 2016, because key reuse across a TLS service and a forgotten SSLv2 service was rampant [@drownsite]. The math of the oracle was 1998&apos;s; only the delivery was new [@drown16].&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;ROBOT (2018): the same oracle, nineteen years on.&lt;/strong&gt; Hanno Bock, Juraj Somorovsky, and Craig Young revisited the original attack and found it alive across the modern internet. The padding-error message was gone, but the oracle now leaked through subtler tells: a TCP reset here, a connection timeout there, a duplicated alert message somewhere else -- any behaviour that differed between conforming and non-conforming padding. It affected almost a third of the top 100 domains, including Facebook and PayPal, and traced to products from F5, Citrix, Radware, Palo Alto Networks, IBM, and Cisco [@robot18].To make the point unmissable, the ROBOT authors used the recovered oracle to sign a message with the private key behind facebook.com&apos;s certificate -- a decryption oracle repurposed to forge a signature, without ever touching the factorization [@robotsite].&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Marvin (2023): pure timing, no error at all.&lt;/strong&gt; The quietest channel yet removes the error entirely. Hubert Kario&apos;s Marvin attack observes only the &lt;em&gt;time&lt;/em&gt; the decryption operation takes, since a conforming and a non-conforming padding often run through subtly different code paths. No alert, no reset, no message -- just a stopwatch. Marvin re-found exploitable leaks in implementations that had been declared immune after ROBOT, and it reaches well beyond TLS into S/MIME, JSON Web Tokens, and hardware tokens such as HSMs and smartcards [@marvin23]. The peer-reviewed write-up appeared at ESORICS 2023 under the fitting title &lt;em&gt;Everlasting ROBOT&lt;/em&gt; [@eprint2023]. Its recommendation was blunt: stop using PKCS#1 v1.5 encryption.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Every member of this lineage leaks the same single bit -- was the padding valid? -- through a different channel. You cannot patch your way to safety one channel at a time, because the next channel is always quieter than the last. The only durable fixes are structural: remove the mode (as TLS 1.3 did) or forbid it (as FIPS did). If a standard forces RSA encryption, use OAEP with constant-time decoding, never v1.5.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Read the four together and the pattern is impossible to miss. The channel gets quieter each time -- an error string in 1998, a cross-protocol zombie in 2016, a TCP quirk in 2018, a bare microsecond in 2023 -- but the leaked bit never changes. This is why &quot;add more padding&quot; was never the answer and constant-time, uniform-failure decoding was. The catalog below is the evidence for this article&apos;s whole argument: every row is some party using the bare trapdoor as if it were a scheme, or letting a check confess.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Attack&lt;/th&gt;
&lt;th&gt;Year&lt;/th&gt;
&lt;th&gt;Front&lt;/th&gt;
&lt;th&gt;Leaked channel&lt;/th&gt;
&lt;th&gt;Root cause&lt;/th&gt;
&lt;th&gt;The rule it teaches&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;Textbook RSA [@rsa78]&lt;/td&gt;
&lt;td&gt;1978&lt;/td&gt;
&lt;td&gt;Encryption&lt;/td&gt;
&lt;td&gt;None needed&lt;/td&gt;
&lt;td&gt;Deterministic, malleable bare permutation&lt;/td&gt;
&lt;td&gt;Never encrypt with the raw primitive&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hastad / Coppersmith [@hastad88, @coppersmith97]&lt;/td&gt;
&lt;td&gt;1988 / 96&lt;/td&gt;
&lt;td&gt;Params&lt;/td&gt;
&lt;td&gt;Algebraic structure&lt;/td&gt;
&lt;td&gt;Small e, short or related messages&lt;/td&gt;
&lt;td&gt;Pad first, use e equals 65537&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bleichenbacher [@bleichenbacher98]&lt;/td&gt;
&lt;td&gt;1998&lt;/td&gt;
&lt;td&gt;Encryption&lt;/td&gt;
&lt;td&gt;Padding-error message&lt;/td&gt;
&lt;td&gt;v1.5 validity check is a decryption oracle&lt;/td&gt;
&lt;td&gt;Uniform, unobservable failure&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Manger [@manger01]&lt;/td&gt;
&lt;td&gt;2001&lt;/td&gt;
&lt;td&gt;Implementation&lt;/td&gt;
&lt;td&gt;Error-type or timing split&lt;/td&gt;
&lt;td&gt;Non-constant-time OAEP decode&lt;/td&gt;
&lt;td&gt;OAEP AND constant-time decoding&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DROWN [@drown16]&lt;/td&gt;
&lt;td&gt;2016&lt;/td&gt;
&lt;td&gt;Encryption&lt;/td&gt;
&lt;td&gt;SSLv2 cross-protocol&lt;/td&gt;
&lt;td&gt;Same RSA key on a weak zombie protocol&lt;/td&gt;
&lt;td&gt;Never reuse keys, kill SSLv2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ROBOT [@robot18]&lt;/td&gt;
&lt;td&gt;2018&lt;/td&gt;
&lt;td&gt;Encryption&lt;/td&gt;
&lt;td&gt;TCP reset, timeout, alert&lt;/td&gt;
&lt;td&gt;v1.5 oracle still live behind subtle tells&lt;/td&gt;
&lt;td&gt;Remove v1.5 key exchange&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Marvin [@marvin23]&lt;/td&gt;
&lt;td&gt;2023&lt;/td&gt;
&lt;td&gt;Implementation&lt;/td&gt;
&lt;td&gt;Decryption timing only&lt;/td&gt;
&lt;td&gt;Non-constant-time v1.5 depadding&lt;/td&gt;
&lt;td&gt;Retire v1.5 encryption entirely&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;e equals 3 forgery [@cve2006]&lt;/td&gt;
&lt;td&gt;2006&lt;/td&gt;
&lt;td&gt;Signature&lt;/td&gt;
&lt;td&gt;None, forged offline&lt;/td&gt;
&lt;td&gt;Lax verifier ignores trailing bytes&lt;/td&gt;
&lt;td&gt;Verify strictly, parse it all&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;BERserk [@cve2014]&lt;/td&gt;
&lt;td&gt;2014&lt;/td&gt;
&lt;td&gt;Signature&lt;/td&gt;
&lt;td&gt;None, forged offline&lt;/td&gt;
&lt;td&gt;NSS mis-parses ASN.1 lengths&lt;/td&gt;
&lt;td&gt;Verify strictly, re-encode and compare&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Bellcore fault [@bdl97]&lt;/td&gt;
&lt;td&gt;1997&lt;/td&gt;
&lt;td&gt;Implementation&lt;/td&gt;
&lt;td&gt;One faulty signature&lt;/td&gt;
&lt;td&gt;CRT fault reveals a prime by gcd&lt;/td&gt;
&lt;td&gt;Verify signature before release&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Kocher, Brumley-Boneh [@kocher96, @brumleyboneh03]&lt;/td&gt;
&lt;td&gt;1996 / 2003&lt;/td&gt;
&lt;td&gt;Implementation&lt;/td&gt;
&lt;td&gt;Exponentiation timing&lt;/td&gt;
&lt;td&gt;Secret-dependent modexp time&lt;/td&gt;
&lt;td&gt;Base blinding, constant time&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Wiener [@wiener90]&lt;/td&gt;
&lt;td&gt;1990&lt;/td&gt;
&lt;td&gt;Params&lt;/td&gt;
&lt;td&gt;Public key structure&lt;/td&gt;
&lt;td&gt;Private exponent d too small&lt;/td&gt;
&lt;td&gt;Never shrink d for speed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Mining Ps and Qs [@miningpq12]&lt;/td&gt;
&lt;td&gt;2012&lt;/td&gt;
&lt;td&gt;Params&lt;/td&gt;
&lt;td&gt;Shared prime factors&lt;/td&gt;
&lt;td&gt;Low boot-time entropy at keygen&lt;/td&gt;
&lt;td&gt;Seed the CSPRNG properly&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ROCA [@roca17]&lt;/td&gt;
&lt;td&gt;2017&lt;/td&gt;
&lt;td&gt;Params&lt;/td&gt;
&lt;td&gt;Public modulus structure&lt;/td&gt;
&lt;td&gt;Infineon&apos;s structured primes&lt;/td&gt;
&lt;td&gt;Generate primes uniformly&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Fourteen rows, one disease. But the encryption oracle is only one of four ways the bare trapdoor betrays its owner. The other three -- the lazy verifier, the glitched chip, and the badly chosen numbers -- are just as instructive, and one of them factors your modulus from a single fault.&lt;/p&gt;
&lt;h2&gt;5. The Rest of the Catalog: Signatures, Faults, and Bad Primes&lt;/h2&gt;
&lt;p&gt;If the encryption oracle is the trapdoor leaking through the decryptor&apos;s &lt;em&gt;answer&lt;/em&gt;, the remaining breaks are the trapdoor leaking through the verifier&apos;s &lt;em&gt;laziness&lt;/em&gt;, the hardware&apos;s &lt;em&gt;faults&lt;/em&gt;, and the &lt;em&gt;numbers&lt;/em&gt; you fed it. Three fronts, and the encryption-versus-signature split stays absolute across all of them.&lt;/p&gt;
&lt;h3&gt;Front A: the signature track, and the precision that governs it&lt;/h3&gt;
&lt;p&gt;Start with a fact that surprises people who lump all of v1.5 together: RFC 8017 records &lt;strong&gt;no known attack against the RSASSA-PKCS1-v1_5 &lt;em&gt;signature scheme itself&lt;/em&gt;&lt;/strong&gt; [@rfc8017]. Unlike v1.5 encryption, the signature padding has never been broken as a construction. It rests on a heuristic argument rather than a tight proof, which is why cryptographers reach for PSS in new designs, but it is legacy-not-broken. Where it &lt;em&gt;is&lt;/em&gt; fragile is at the verifier -- and that fragility has bitten twice.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The $e = 3$ forgery (2006, CVE-2006-4339).&lt;/strong&gt; Daniel Bleichenbacher -- the same name, a different result eight years later -- described a forgery that needs no private key at all. A v1.5 signature block ends with the ASN.1 &lt;code&gt;DigestInfo&lt;/code&gt; structure, and a lazy verifier checks that the high-order bytes look right without confirming that the &lt;code&gt;DigestInfo&lt;/code&gt; sits flush against the end with no trailing data.&lt;/p&gt;
&lt;p&gt;With a small public exponent such as $e = 3$, an attacker can construct a number whose cube reproduces the correct prefix and hash in the leading bytes, then pack arbitrary garbage into the trailing bytes the verifier never inspects. Because cubing is cheap and the low bytes are free, the forged &quot;signature&quot; is just a carefully chosen cube root [@cve2006]. OpenSSL&apos;s advisory of 5 September 2006 traced it to exactly that omission: &quot;not checking for excess data&quot; after the exponentiation [@openssl2006].&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;BERserk (2014, CVE-2014-1568).&lt;/strong&gt; Eight years on, the same antipattern returned in a different guise. Mozilla&apos;s NSS library mis-parsed ASN.1 length fields during signature verification, letting an attacker smuggle forged content past the check and produce signatures that NSS -- and therefore Firefox and Chrome builds of the era -- accepted as valid, including for TLS certificates [@cve2014]. Once again, no scheme was broken; a verifier was.&lt;/p&gt;
&lt;p&gt;The recurring lesson is one line: &lt;strong&gt;verify strictly.&lt;/strong&gt; Parse the entire structure, reject any trailing data, re-encode the expected block and compare it byte-for-byte, and never take an $e = 3$ shortcut. The provable alternative, PSS, removes the temptation to hand-roll a lenient check by making the encoding randomized and its verification total; its mechanism is in the next section [@pss96]. These verifier bugs live where v1.5 signatures live -- inside the &lt;a href=&quot;https://paragmali.com/blog/a-perfect-signature-for-a-certificate-that-should-never-have/&quot; rel=&quot;noopener&quot;&gt;X.509 and PKI machinery&lt;/a&gt; that Part 4 of this series covers.&lt;/p&gt;

&quot;Still-deployed PKCS#1 v1.5&quot; hides two opposite truths, and conflating them is the single most common error in RSA writing. v1.5 *encryption* (RSAES-PKCS1-v1_5) is fundamentally dangerous padding: its validity check is a decryption oracle, and it should be retired. v1.5 *signatures* (RSASSA-PKCS1-v1_5) are dominant across Web PKI, FIPS-approved in FIPS 186-5, and unbroken as a scheme -- keep them where mandated and verify strictly [@rfc8017], [@fips1865]. One phrase must never carry both meanings. When you read &quot;RSA v1.5 is broken,&quot; ask which one, because the honest answer is &quot;the encryption, not the signatures.&quot;
&lt;h3&gt;Front B: the physical layer&lt;/h3&gt;
&lt;p&gt;Here the secret &lt;em&gt;is&lt;/em&gt; the factorization, so anything the hardware leaks about the private operation leaks the factors directly.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The CRT fault (Boneh, DeMillo, and Lipton, 1997).&lt;/strong&gt; Recall that fast RSA signing computes the result modulo $p$ and modulo $q$ separately, then recombines. Suppose a single bit flips during the modulo-$p$ half -- from a voltage glitch, a clock fault, cosmic radiation, or a deliberate injection. The faulty signature $s&apos;$ is now correct modulo $q$ but wrong modulo $p$. Then $s&apos;^e - m$ is divisible by $q$ but not by $p$, so a single $\gcd(s&apos;^e - m, N)$ hands you $q$ -- and the modulus is factored from &lt;em&gt;one&lt;/em&gt; faulty signature [@bdl97]. The demonstration below does exactly that with toy primes.&lt;/p&gt;
&lt;p&gt;{`
// Bellcore fault: a fault in one CRT half leaks a prime of N. Toy primes for clarity.
function modpow(base, exp, mod) {
  base = ((base % mod) + mod) % mod; let r = 1n;
  while (exp &amp;gt; 0n) { if (exp &amp;amp; 1n) r = (r * base) % mod; base = (base * base) % mod; exp &amp;gt;&amp;gt;= 1n; }
  return r;
}
function gcd(a, b) { while (b) { const t = a % b; a = b; b = t; } return a; }
const p = 61n, q = 53n, N = p * q, e = 17n, d = 2753n;
const m = 123n % N;                       // message representative to sign
const dp = d % (p - 1n), dq = d % (q - 1n);
const qInv = modpow(q, p - 2n, p);&lt;/p&gt;
&lt;p&gt;// Correct CRT signature (Garner recombination):
const sp = modpow(m, dp, p), sq = modpow(m, dq, q);
const s = (sq + q * (((qInv * (sp - sq)) % p + p) % p)) % N;
console.log(&apos;correct signature verifies?&apos;, modpow(s, e, N) === m);&lt;/p&gt;
&lt;p&gt;// A fault corrupts ONLY the mod-p half:
const spBad = (sp + 1n) % p;
const sBad = (sq + q * (((qInv * (spBad - sq)) % p + p) % p)) % N;
console.log(&apos;faulty  signature verifies?&apos;, modpow(sBad, e, N) === m);&lt;/p&gt;
&lt;p&gt;// One gcd factors the modulus:
const diff = ((modpow(sBad, e, N) - m) % N + N) % N;
console.log(&apos;gcd(sBad^e - m, N) =&apos;, gcd(diff, N).toString(), &apos;-&amp;gt; a secret prime of N (61 or 53)&apos;);
`}&lt;/p&gt;
&lt;p&gt;The fix is a single line of discipline: &lt;strong&gt;verify before release.&lt;/strong&gt; Recompute $s^e \bmod N$ and confirm it equals $m$ before the signature ever leaves the chip. A faulty signature never escapes, and the gcd trick has nothing to work with.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Timing (Kocher, 1996; Brumley and Boneh, 2003).&lt;/strong&gt; Paul Kocher observed in 1996 that the time to compute $c^d \bmod N$ depends on the secret bits of $d$, because square-and-multiply does extra work on &lt;code&gt;1&lt;/code&gt; bits [@kocher96]. Folklore held that timing attacks needed local access. David Brumley and Dan Boneh demolished that in 2003 by recovering an OpenSSL server&apos;s private key &lt;em&gt;over a network&lt;/em&gt;, measuring only response times [@brumleyboneh03].&lt;/p&gt;
&lt;p&gt;The fix is &lt;strong&gt;base blinding&lt;/strong&gt;: multiply the input by $r^e$ for a fresh random $r$ before exponentiating, then divide the result by $r$ afterward, so the timing depends on a value the attacker cannot predict. OpenSSL turned blinding on by default in 2003. Marvin, from the previous section, is this front&apos;s 2023 revival -- the timing channel never truly closed.&lt;/p&gt;
&lt;h3&gt;Front C: the parameters&lt;/h3&gt;
&lt;p&gt;The trapdoor is only as strong as the numbers behind it.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Wiener (1990).&lt;/strong&gt; If you shrink the private exponent for speed so that $d &amp;lt; \tfrac{1}{3} N^{1/4}$, a continued-fraction expansion of $e/N$ recovers $d$ outright [@wiener90]. You cannot buy decryption speed by making the secret small.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Coppersmith (1996 to 1997).&lt;/strong&gt; His lattice method sets the barrier $|x| &amp;lt; N^{1/e}$ that both &lt;em&gt;enables&lt;/em&gt; low-exponent attacks on short or stereotyped messages and &lt;em&gt;bounds&lt;/em&gt; them -- the same result that returns, weaponized, as ROCA [@coppersmith97].&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Mining Your Ps and Qs (2012).&lt;/strong&gt; Heninger and colleagues scanned the internet&apos;s public keys and computed pairwise gcds. Devices that generated keys at first boot, before they had gathered entropy, sometimes shared a prime -- and a shared prime means a free gcd factors both moduli. They computed private keys for about 0.50 percent of TLS hosts and 0.03 percent of SSH hosts this way [@miningpq12].&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ROCA (2017, CVE-2017-15361).&lt;/strong&gt; Infineon&apos;s key-generation library built primes with a special structure to speed things up. That structure let a Coppersmith attack factor the public modulus for a practical cost, and the affected chips were everywhere: TPMs, YubiKey 4 tokens, national electronic ID cards, and BitLocker deployments [@roca17], [@cve2017]. Coppersmith&apos;s 1996 barrier, patient for twenty years, collected its debt.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The root cause of the last two is not RSA math at all -- it is the &lt;a href=&quot;https://paragmali.com/blog/predictable-or-repeated-the-only-two-ways-cryptographic-rand/&quot; rel=&quot;noopener&quot;&gt;CSPRNG that generates the primes&lt;/a&gt;, the subject of Part 2 of this series. Feed RSA weak randomness and no scheme, no padding, and no proof can save it.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Turn the diagnostic question on each front and it answers itself. The verifier confesses through a check it skipped. The chip confesses through a fault it did not catch. The clock confesses through a branch it did not equalize. The keygen confesses through primes it did not draw uniformly. In every case the attacker reads the private key off the receiver&apos;s &lt;em&gt;behaviour&lt;/em&gt;, not off the mathematics.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Keep the empirical yardstick in view: the largest RSA modulus ever publicly factored is RSA-250, at 829 bits.RSA-250 was factored on 28 February 2020 using the Number Field Sieve, at a cost of roughly 2,700 core-years of computation [@rsa250]. That is the real, measured cost of breaking RSA by its front door -- which is exactly why nobody attacks that door when a padding check will open the side gate for a million cheap queries. Four fronts, one disease. The cure is not a cleverer patch on any single front. It is a change in what we mean when we say a scheme is secure.&lt;/p&gt;
&lt;h2&gt;6. Security Is a Property of the Scheme AND the Implementation&lt;/h2&gt;
&lt;p&gt;There are two famous ideas behind &quot;RSA done right,&quot; and a third, deeper one that the first two kept teaching the hard way.&lt;/p&gt;
&lt;h3&gt;Idea 1: OAEP, the all-or-nothing randomized transform&lt;/h3&gt;
&lt;p&gt;Optimal Asymmetric Encryption Padding, designed by Mihir Bellare and Phillip Rogaway in 1994, encodes the message before the RSA permutation so that the encoded block has no gradable structure for an attacker to probe [@oaep94]. Its construction is a two-round Feistel network with a hash-based mask-generation function, MGF1, as the round function -- the &lt;a href=&quot;https://paragmali.com/blog/the-fingerprint-two-files-shared-a-field-guide-to-cryptograp/&quot; rel=&quot;noopener&quot;&gt;hashing machinery&lt;/a&gt; that Part 10 of this series develops. Writing $\Vert$ for concatenation and $\oplus$ for XOR, and starting from a random $\mathrm{seed}$:&lt;/p&gt;
&lt;p&gt;$$\mathrm{DB} = \mathrm{lHash} \Vert \mathrm{PS} \Vert \texttt{0x01} \Vert M$$
$$\mathrm{maskedDB} = \mathrm{DB} \oplus \mathrm{MGF1}(\mathrm{seed}), \qquad \mathrm{maskedSeed} = \mathrm{seed} \oplus \mathrm{MGF1}(\mathrm{maskedDB})$$
$$\mathrm{EM} = \texttt{0x00} \Vert \mathrm{maskedSeed} \Vert \mathrm{maskedDB}$$&lt;/p&gt;
&lt;p&gt;Then the RSA permutation is applied to $\mathrm{EM}$ [@rfc8017]. The two Feistel rounds make every bit of the encoded block depend on every bit of the message &lt;em&gt;and&lt;/em&gt; the random seed. That is the property that kills the padding oracle: flip a single bit of an OAEP ciphertext and the decoded block is randomized wholesale, so a tampered ciphertext decodes to noise with overwhelming probability. There is no conformant interval to bisect, no structured remnant to grade.&lt;/p&gt;
&lt;p&gt;With the randomness supplying non-determinism and the all-or-nothing mixing supplying tamper-detection, OAEP is not only semantically secure but also non-malleable and secure against chosen-ciphertext attack -- IND-CCA2 in the random-oracle model [@oaep94abs].&lt;/p&gt;

Optimal Asymmetric Encryption Padding: a randomized, all-or-nothing transform applied to a message before the RSA permutation. It mixes the message with a random seed through two mask-generation passes -- a two-round Feistel network -- so that every bit of the encoded block depends on every bit of the input. Flipping any ciphertext bit randomizes the whole decoded block, which destroys malleability and yields chosen-ciphertext security in the random-oracle model.

OAEP&apos;s history includes a moment cryptography should be proud of. In 2001, Victor Shoup showed that the celebrated 1994 security proof did not go through for an arbitrary trapdoor permutation -- it quietly assumed more than the definition guarantees [@shoup01]. The scheme was not broken; the *argument* was. Later that year, Fujisaki, Okamoto, Pointcheval, and Stern repaired it, proving RSA-OAEP is chosen-ciphertext secure in the random-oracle model under the RSA assumption, by leaning on a property called partial-domain one-wayness -- though the resulting reduction is non-tight [@fops04]. A public &quot;our proof, not our scheme, was wrong,&quot; followed by a public fix, is exactly how a mature field is supposed to work.
&lt;h3&gt;Idea 2: PSS, the salted encoding with a tight proof&lt;/h3&gt;
&lt;p&gt;For signatures, the Probabilistic Signature Scheme, also from Bellare and Rogaway, plays the analogous role [@pss96]. To sign, it hashes the message to $\mathrm{mHash}$, draws a random $\mathrm{salt}$, and forms $M&apos; = \texttt{padding} \Vert \mathrm{mHash} \Vert \mathrm{salt}$; sets $H = \mathrm{Hash}(M&apos;)$; builds $\mathrm{DB} = \mathrm{PS} \Vert \texttt{0x01} \Vert \mathrm{salt}$; masks it as $\mathrm{maskedDB} = \mathrm{DB} \oplus \mathrm{MGF1}(H)$; and assembles $\mathrm{EM} = \mathrm{maskedDB} \Vert H \Vert \texttt{0xbc}$ before applying the RSA private operation [@rfc8017].&lt;/p&gt;
&lt;p&gt;The randomization and MGF mixing buy something v1.5 signatures never had: a &lt;em&gt;tight&lt;/em&gt; exact-security reduction. A forger against PSS implies an algorithm that inverts RSA at almost the same cost -- so breaking the signatures is essentially as hard as the RSA problem itself, not merely conjectured to be [@pss96]. That is strictly stronger than v1.5&apos;s heuristic argument. PSS entered the standard in PKCS#1 v2.1 (RFC 3447) in 2003 and remains in the current v2.2 [@rfc3447]. Yet the split holds: PSS is the provable &lt;em&gt;upgrade&lt;/em&gt;, but v1.5 signatures are not superseded in deployment -- they remain the Web PKI default.Beyond these two deployed schemes, the literature holds academic-only refinements -- SAEP/SAEP+, OAEP+, three-round OAEP, tight RSA-KEM variants, and message-recovery PSS-R -- which tighten proofs or trim assumptions but never displaced OAEP and PSS in practice. A pointer, not a section.&lt;/p&gt;

Probabilistic Signature Scheme: a randomized, salted RSA signature encoding. A fresh random salt and MGF1 mixing make every signature of the same message different, and the scheme comes with a tight security reduction -- a forger would yield an almost-equally-efficient algorithm for inverting RSA, the strongest guarantee any RSA scheme offers.
&lt;h3&gt;Idea 3: the one the first two kept teaching&lt;/h3&gt;
&lt;p&gt;Here is where the whole article turns. OAEP is provably secure, yet Manger re-opened the identical oracle inside a non-constant-time &lt;em&gt;decoder&lt;/em&gt;. PSS is provably secure, yet a fault or a timing leak in the &lt;em&gt;signer&lt;/em&gt; factors the key. The scheme was never the whole story.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key idea:&lt;/strong&gt; Security is a property of the scheme AND its implementation, not of the math. A perfect scheme run by a decoder that leaks whether padding verified is exactly as broken as no scheme at all. The implementation disciplines below are part of the construction, not optional hygiene layered on top.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Three disciplines close the three channels the catalog exposed:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Constant-time depadding with implicit rejection.&lt;/strong&gt; On any decryption failure, do not branch or return a distinguishable error; substitute a deterministic pseudo-random value and continue, so whether the padding verified stays unobservable. OpenSSL 3.2 ships this by default and aligned its behaviour with NSS so neither library can be an oracle for the other [@openssl32], [@gorsa].&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CRT verify-before-release.&lt;/strong&gt; Recompute and check the signature before it leaves the device, defeating Bellcore faults [@bdl97].&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Base blinding.&lt;/strong&gt; Randomize the input before exponentiation so timing reveals nothing about the secret [@kocher96], [@brumleyboneh03].&lt;/li&gt;
&lt;/ul&gt;

A decryption strategy in which a padding failure does not produce a distinguishable outcome. Instead of branching or returning a specific error, the code substitutes a deterministic pseudo-random value derived from the private key and ciphertext and proceeds. Success and failure become indistinguishable through content, error type, or timing, so there is no oracle left to query.

flowchart TD
    M[Message or ciphertext arrives] --&amp;gt; SCH{&quot;Scheme layer&quot;}
    SCH --&amp;gt;|Encrypt| OAEP[RSAES-OAEP, randomized all-or-nothing]
    SCH --&amp;gt;|Sign| PSS[RSASSA-PSS, salted with tight proof]
    OAEP --&amp;gt; IMPL{&quot;Implementation layer&quot;}
    PSS --&amp;gt; IMPL
    IMPL --&amp;gt; CT[Constant-time depad, implicit rejection]
    IMPL --&amp;gt; VBR[CRT verify-before-release]
    IMPL --&amp;gt; BL[Base blinding]
    CT --&amp;gt; PAR{&quot;Parameter layer&quot;}
    VBR --&amp;gt; PAR
    BL --&amp;gt; PAR
    PAR --&amp;gt; P1[Modulus at least 2048 bits, e equals 65537, CSPRNG primes]
    P1 --&amp;gt; GATE{&quot;Does any behaviour reveal the padding verdict?&quot;}
    GATE --&amp;gt;|No| SAFE[RSA done right]
    GATE --&amp;gt;|Yes| BROKEN[Oracle reopens, the catalog repeats]
&lt;p&gt;The libraries now say this in plain language. Go&apos;s standard library deprecated its v1.5 decryption function outright, with a warning that doubles as the thesis of this article:&lt;/p&gt;

&quot;PKCS #1 v1.5 encryption is dangerous and should not be used ... whether this function returns an error or not discloses secret information.&quot; -- Go crypto/rsa package documentation [@gorsa]
&lt;p&gt;That is the whole discipline in one sentence: done right closes the padding-oracle class Part 6 traces across all of cryptography, and it does so at the implementation layer, not the math. So what does the whole stack look like in 2024 to 2026 -- and which parts are quietly being switched off?&lt;/p&gt;
&lt;h2&gt;7. State of the Art (2024 to 2026): Done Right Is a Stack, Not a Method&lt;/h2&gt;
&lt;p&gt;Most primitives have a single &quot;best&quot; option you can name. RSA does not. &quot;Done right&quot; in 2026 is a &lt;em&gt;conjunction&lt;/em&gt; of independent choices that must all hold at once. Remove any one and the 25-year catalog reopens at that layer. Here is the stack, layer by layer.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Mode&lt;/th&gt;
&lt;th&gt;Job&lt;/th&gt;
&lt;th&gt;What it adds&lt;/th&gt;
&lt;th&gt;Security status&lt;/th&gt;
&lt;th&gt;Still requires&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;v1.5 encryption&lt;/td&gt;
&lt;td&gt;Encrypt&lt;/td&gt;
&lt;td&gt;Randomness only&lt;/td&gt;
&lt;td&gt;Broken as a target -- padding oracle&lt;/td&gt;
&lt;td&gt;Retire it; no safe deployment&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RSAES-OAEP&lt;/td&gt;
&lt;td&gt;Encrypt&lt;/td&gt;
&lt;td&gt;Randomness plus all-or-nothing mixing&lt;/td&gt;
&lt;td&gt;IND-CCA2 in the random-oracle model&lt;/td&gt;
&lt;td&gt;Constant-time, implicit-rejection decode&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;v1.5 signature&lt;/td&gt;
&lt;td&gt;Sign&lt;/td&gt;
&lt;td&gt;Fixed checkable structure&lt;/td&gt;
&lt;td&gt;Unbroken scheme, heuristic argument&lt;/td&gt;
&lt;td&gt;Strict verification, no e equals 3 shortcut&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RSASSA-PSS&lt;/td&gt;
&lt;td&gt;Sign&lt;/td&gt;
&lt;td&gt;Salt plus MGF, tight proof&lt;/td&gt;
&lt;td&gt;Provably as hard as the RSA problem&lt;/td&gt;
&lt;td&gt;CRT verify-before-release, blinding&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;Encryption scheme.&lt;/strong&gt; When a standard genuinely mandates RSA encryption of a small payload, the answer is RSAES-OAEP with SHA-256 and MGF1-SHA-256, decoded in constant time [@rfc8017]. But note the capacity ceiling: a 2,048-bit OAEP-SHA-256 ciphertext carries at most about 190 bytes of plaintext [@sp80056b]. &quot;Encrypt a file with RSA-OAEP&quot; is therefore not just risky but physically wrong -- RSA encryption is for keys, never bulk data.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Signatures.&lt;/strong&gt; New designs use RSASSA-PSS, which TLS 1.3 makes mandatory for handshake signatures [@pss96], [@rfc8446]. Yet RSASSA-PKCS1-v1_5 signatures remain dominant and FIPS-approved across X.509 and TLS certificates [@fips1865]. This is not a contradiction: keep v1.5 signatures where they are mandated, and verify them strictly [@rfc8017].&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Implementation hardening.&lt;/strong&gt; This is the layer that actually stops the attacks. OpenSSL 3.2 enables constant-time depadding with implicit rejection by default [@openssl32], Go has deprecated its v1.5 decryption entry point [@gorsa], and the CFRG&apos;s 2025 implementation-guidance draft folds all of this into formal advice amending RFC 8017 [@cfrgdraft].&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Parameters.&lt;/strong&gt; A 2,048-bit modulus is the floor for 112-bit security; use 3,072 or 4,096 bits for long-term secrets; keep $e = 65537$; and draw primes from a properly seeded CSPRNG [@sp80057], [@fips1865].&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Deployment and policy.&lt;/strong&gt; The most durable fixes were structural, not cryptographic. TLS 1.3 &lt;em&gt;deleted&lt;/em&gt; RSA key exchange entirely, which killed the Bleichenbacher-on-TLS class at the protocol level rather than patching each oracle [@rfc8446]. FIPS policy disallows RSA v1.5 key transport after 31 December 2023 [@sp800131a], [@sp80056b]. And NIST IR 8547 puts a clock on RSA itself, deprecating 112-bit strength after 2030 and disallowing it after 2035 [@ir8547].&lt;/p&gt;

If you operate under FIPS 140-3, the encryption side of this is not advisory. NIST SP 800-131A Rev. 2 and SP 800-56B Rev. 2 together disallow RSAES-PKCS1-v1_5 key transport after 31 December 2023; approved RSA key establishment must use OAEP [@sp800131a], [@sp80056b]. The signature side is the opposite: RSASSA-PKCS1-v1_5 signatures remain FIPS-approved under FIPS 186-5 [@fips1865]. Same version number, opposite compliance verdicts -- which is exactly why the encryption-signature split is not pedantry.
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Right scheme (OAEP to encrypt, PSS for new signatures), right implementation (constant-time depad with implicit rejection, CRT verify-before-release, base blinding), right parameters (2,048-bit floor, e equals 65537, CSPRNG primes), inside the right protocol (TLS 1.3, no RSA key exchange). Every deployed disaster in the catalog removed exactly one of these. None of it is new in 2026; the interesting movement is not toward a better RSA but away from RSA entirely.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;RSA done right is &lt;em&gt;stable&lt;/em&gt;. The genuinely current story is mostly about what to switch off and what is migrating away -- because for both of RSA&apos;s jobs, something smaller and faster is already taking over.&lt;/p&gt;
&lt;h2&gt;8. What RSA Is Losing To: ECDH, KEM-DEM, and the Post-Quantum Elephant&lt;/h2&gt;
&lt;p&gt;RSA is not being fixed into the future; it is being replaced out of it -- and the reasons have nothing to do with padding.&lt;/p&gt;
&lt;p&gt;The cleanest of those reasons is a design pattern that removes the attacker&apos;s target entirely. Never RSA-encrypt data. Instead, encapsulate a fresh random &lt;em&gt;symmetric&lt;/em&gt; key with the public key, and let an authenticated cipher carry the bulk. The public-key operation then transports only a uniform random key -- there is no attacker-chosen, structured plaintext for any oracle to grade. The entire Bleichenbacher-to-Marvin family loses its object, because the thing being decrypted is indistinguishable from random by construction [@rfc8446].&lt;/p&gt;

A two-part construction for hybrid encryption. A Key Encapsulation Mechanism (KEM) uses the public key to transport a freshly generated random symmetric key; a Data Encapsulation Mechanism (DEM) then encrypts the actual data with that key under an authenticated cipher. Because the public-key step only ever carries a uniform random key -- never a chosen, structured message -- there is no padding structure for an oracle to probe, so the padding-oracle class simply does not apply.

sequenceDiagram
    participant S as Sender
    participant R as Recipient
    Note over R: Publishes a public key
    S-&amp;gt;&amp;gt;S: Encapsulate, generate a random key K for the recipient
    S-&amp;gt;&amp;gt;S: Stretch K with a KDF, AEAD-encrypt the data
    S-&amp;gt;&amp;gt;R: Send the encapsulation and the AEAD ciphertext
    R-&amp;gt;&amp;gt;R: Decapsulate to recover K, run the same KDF
    R-&amp;gt;&amp;gt;R: AEAD-decrypt and verify the data
    Note over R,S: The public-key part carried only a random K, nothing to grade
&lt;p&gt;This is why elliptic-curve key agreement (ECDH, with Ed25519 and ECDSA for signatures) displaced RSA key transport: it does the same jobs with far smaller keys, faster operations, and forward secrecy -- a property RSA key transport never had, and the direct reason TLS 1.3 could &lt;em&gt;delete&lt;/em&gt; RSA key exchange rather than merely patch it [@rfc8446]. The &lt;a href=&quot;https://paragmali.com/blog/the-tag-verified-the-cipher-held-the-forgery-went-through-a-/&quot; rel=&quot;noopener&quot;&gt;KEM-DEM composition&lt;/a&gt; is developed in Part 11 of this series, the KDF step in Part 13, and the &lt;a href=&quot;https://paragmali.com/blog/the-aead-decision-matrix-seven-ciphers-three-edges-one-choic/&quot; rel=&quot;noopener&quot;&gt;AEAD that carries the payload&lt;/a&gt; in Part 7.&lt;/p&gt;
&lt;p&gt;Then there is the elephant. Peter Shor&apos;s algorithm factors integers in polynomial time on a large fault-tolerant quantum computer, which makes &lt;em&gt;all&lt;/em&gt; factoring-based cryptography -- every RSA key length, done right or not -- eventually breakable. NIST has already named the destination: ML-KEM (FIPS 203) for key encapsulation, and ML-DSA and SLH-DSA (FIPS 204 and 205) for signatures [@fips203], [@fips204], [@fips205].&lt;/p&gt;
&lt;p&gt;The migration is lopsided. Key encapsulation is moving fast -- hybrid X25519 plus ML-KEM-768 already carries a double-digit percentage of Cloudflare&apos;s TLS 1.3 traffic -- while post-quantum &lt;em&gt;signatures&lt;/em&gt; lag badly, because they run 10 to 200 times larger than an RSA signature and no public post-quantum certificate infrastructure existed before roughly 2026 [@cloudflare24], [@ir8547]. The lattice and hash-based internals belong to a later part of this series; here they matter only as RSA&apos;s replacement, not its repair.&lt;/p&gt;

Quantum risk is not only a future problem. An adversary can record RSA-encrypted traffic today and decrypt it once a capable quantum computer exists. For any secret that must stay confidential past roughly 2030, the exposure is *present tense* -- which is why NIST IR 8547 frames the transition as urgent rather than eventual, and why hybrid key exchange is being deployed now rather than when the machine arrives [@ir8547], [@cloudflare24].
&lt;p&gt;Laid side by side, the trade-offs explain why key transport migrated first and signatures are dragging. For moving a key:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Key transport&lt;/th&gt;
&lt;th&gt;Forward secrecy&lt;/th&gt;
&lt;th&gt;Padding-oracle exposure&lt;/th&gt;
&lt;th&gt;Relative cost&lt;/th&gt;
&lt;th&gt;Quantum status&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;RSAES-OAEP [@rfc8017]&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes, if decode is not constant-time&lt;/td&gt;
&lt;td&gt;Large ciphertext, slow keygen&lt;/td&gt;
&lt;td&gt;Broken by Shor&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ECDH (X25519) [@rfc8446]&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;None, no RSA decryption&lt;/td&gt;
&lt;td&gt;Small and fast&lt;/td&gt;
&lt;td&gt;Broken by Shor&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ML-KEM-768 [@fips203]&lt;/td&gt;
&lt;td&gt;Yes (ephemeral)&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;Kilobyte-scale, fast&lt;/td&gt;
&lt;td&gt;Resistant&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hybrid X25519 + ML-KEM-768 [@cloudflare24]&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;Sum of both, still practical&lt;/td&gt;
&lt;td&gt;Resistant if either holds&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;And for signing:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Signature&lt;/th&gt;
&lt;th&gt;Provable security&lt;/th&gt;
&lt;th&gt;Approx. signature size&lt;/th&gt;
&lt;th&gt;Main failure mode&lt;/th&gt;
&lt;th&gt;Quantum status&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;RSASSA-PSS [@pss96]&lt;/td&gt;
&lt;td&gt;Yes, tight in ROM&lt;/td&gt;
&lt;td&gt;256 bytes at 2048-bit&lt;/td&gt;
&lt;td&gt;Fault or timing in the signer&lt;/td&gt;
&lt;td&gt;Broken by Shor&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RSASSA-PKCS1-v1_5 [@rfc8017]&lt;/td&gt;
&lt;td&gt;No, heuristic only&lt;/td&gt;
&lt;td&gt;256 bytes at 2048-bit&lt;/td&gt;
&lt;td&gt;Lax-verifier forgery&lt;/td&gt;
&lt;td&gt;Broken by Shor&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ECDSA / Ed25519 [@rfc8446]&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;64 bytes&lt;/td&gt;
&lt;td&gt;Nonce reuse (ECDSA)&lt;/td&gt;
&lt;td&gt;Broken by Shor&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ML-DSA-65 [@fips204]&lt;/td&gt;
&lt;td&gt;Yes, lattice&lt;/td&gt;
&lt;td&gt;About 3.3 kilobytes&lt;/td&gt;
&lt;td&gt;Implementation bugs&lt;/td&gt;
&lt;td&gt;Resistant&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SLH-DSA [@fips205]&lt;/td&gt;
&lt;td&gt;Yes, hash-based&lt;/td&gt;
&lt;td&gt;Many kilobytes&lt;/td&gt;
&lt;td&gt;Large and slow&lt;/td&gt;
&lt;td&gt;Resistant&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;The signature column is the migration&apos;s hard problem, which is why post-quantum certificates lag key exchange by years [@ir8547]. If RSA is on the clock, it is worth asking where its security actually came from in the first place -- and why nobody has ever proved it was there.&lt;/p&gt;
&lt;h2&gt;9. Theoretical Limits: Where the Security Comes From, and Its Ceiling&lt;/h2&gt;
&lt;p&gt;Here is the uncomfortable truth a first course skips: there is no proof that RSA is secure.&lt;/p&gt;
&lt;p&gt;Start with the assumption itself. The RSA problem is to compute $e$-th roots modulo $N$ without the factorization. We know this is &lt;em&gt;no harder&lt;/em&gt; than factoring -- if you can factor $N$, you can compute $d$ and invert everything, so RSA $\le$ factoring. What nobody has shown is the reverse. It is an open question whether breaking RSA is &lt;em&gt;equivalent&lt;/em&gt; to factoring, and Boneh and Venkatesan gave evidence that a broad class of algebraic reductions from factoring to low-exponent RSA is unlikely to exist -- suggesting the two problems may not be equivalent at all [@bv98].&lt;/p&gt;
&lt;p&gt;Worse, factoring itself is not known to be hard in any proven sense: it sits in NP intersect co-NP, which is evidence it is probably &lt;em&gt;not&lt;/em&gt; NP-complete, and no one has proved a super-polynomial lower bound for it. RSA&apos;s security is heuristic and empirical -- it has survived decades of attack, and that is the entire argument [@boneh99].&lt;/p&gt;
&lt;p&gt;The empirical ceiling is concrete. The best classical algorithm, the General Number Field Sieve, runs in sub-exponential time, and the public record is RSA-250 at 829 bits, factored in February 2020 for roughly 2,700 core-years [@rsa250]. RSA-250 was one of the RSA Factoring Challenge moduli that RSA Laboratories first published in 1991 as public benchmarks for exactly this kind of progress [@rsanumbers]. That record sets the practical floors:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;RSA modulus&lt;/th&gt;
&lt;th&gt;Symmetric-equivalent strength&lt;/th&gt;
&lt;th&gt;Status&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;1024-bit&lt;/td&gt;
&lt;td&gt;About 80 bits&lt;/td&gt;
&lt;td&gt;Broken within reach, disallowed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2048-bit&lt;/td&gt;
&lt;td&gt;112 bits&lt;/td&gt;
&lt;td&gt;Current minimum, deprecated after 2030&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3072-bit&lt;/td&gt;
&lt;td&gt;128 bits&lt;/td&gt;
&lt;td&gt;Long-term use&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4096-bit&lt;/td&gt;
&lt;td&gt;About 140 bits (interpolated)&lt;/td&gt;
&lt;td&gt;High assurance&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7680-bit&lt;/td&gt;
&lt;td&gt;192 bits&lt;/td&gt;
&lt;td&gt;Next tabulated SP 800-57 step&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;15360-bit&lt;/td&gt;
&lt;td&gt;256 bits&lt;/td&gt;
&lt;td&gt;Shows how badly RSA scales&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Those equivalences come from NIST SP 800-57, except the 4,096-bit figure -- a common interpolation, since the standard steps directly from 3,072 bits (128) to 7,680 bits (192). The last row is the quiet punchline: matching a 256-bit symmetric key needs a 15,360-bit RSA modulus, because RSA strength grows only sub-exponentially in key length while the cost of using it grows with the cube [@sp80057]. RSA scales badly, and that alone pushes new systems toward elliptic curves.&lt;/p&gt;
&lt;p&gt;Then the cliff. On a large fault-tolerant quantum computer, Shor&apos;s algorithm factors in polynomial time -- not faster, but &lt;em&gt;categorically&lt;/em&gt; faster, collapsing the whole assumption. The only question is engineering, and the estimates are falling fast. In 2019, Gidney and Ekera estimated 20 million noisy qubits and 8 hours to factor a 2,048-bit modulus [@gidney19]; by 2025, Gidney had cut the qubit estimate to under a million [@gidney25].A 20-fold reduction in the resource estimate in six years, with no fundamental barrier in sight, is precisely the trend that drove NIST to put firm dates -- 2030 and 2035 -- on RSA&apos;s retirement in IR 8547. The deadline is set by the slope, not by any single machine.&lt;/p&gt;
&lt;p&gt;Three impossibility results frame the whole subject, each already seen in this article. First, determinism can never be IND-CPA -- that is structural, not fixable, which is why padding must randomize. Second, there is no known standard-model proof of IND-CCA2 for RSA-OAEP under the plain RSA assumption; the guarantee is random-oracle-model only, and even there non-tight [@shoup01], [@fops04]. Third, Coppersmith&apos;s barrier $|x| &amp;lt; N^{1/e}$ both enables low-exponent attacks and bounds them, a hard mathematical edge that no parameter choice moves [@coppersmith97].&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key idea:&lt;/strong&gt; RSA&apos;s safety lives in an unproven gap -- breaking it is no harder than factoring, but maybe strictly easier, and factoring is not even proven hard -- and that gap sits on the near side of a quantum cliff with a falling date on it. &quot;Done right&quot; buys you security against every known classical attack. It does not buy you a proof, and it cannot buy you time past Shor.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;If the ground under RSA is this uncertain, what exactly is still unsettled -- and which of those open problems can bite you today?&lt;/p&gt;
&lt;h2&gt;10. Open Problems: What Remains Genuinely Unsettled&lt;/h2&gt;
&lt;p&gt;Some of these are for theorists. Others are live in your dependency tree right now.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Is the RSA problem equivalent to factoring?&lt;/strong&gt; We have only one direction, RSA $\le$ factoring; the reverse is open, with evidence it may fail [@bv98]. It matters because RSA&apos;s whole security story rests on a hardness we have never actually pinned down.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;A deployed, standard-model IND-CCA2 RSA scheme.&lt;/strong&gt; Constructions that avoid the random-oracle model exist on paper, but none is a shipping default; practice sidesteps the gap entirely by using KEM-DEM instead of RSA encryption [@fops04].&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Constant-time RSA is a perpetually moving target.&lt;/strong&gt; Marvin re-found timing leaks in implementations previously believed immune, and only a handful -- such as BearSSL and BoringSSL -- passed its tests; leaks hide in general-purpose bignum code and even in error-logging paths, which is why the CFRG draft concludes the only safe path is to deprecate v1.5 encryption outright rather than keep hardening it [@marvin23], [@cfrgdraft].&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The long tail of v1.5 encryption.&lt;/strong&gt; HSMs, PKCS#11 tokens, S/MIME, and JWE still use it in places that cannot simply be switched off -- exactly where Marvin keeps finding live oracles [@marvin23], [@gorsa].&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Generation-time entropy and structured primes.&lt;/strong&gt; Mining Your Ps and Qs and ROCA are operational failures with no clean universal fix: you cannot prove every device in the world seeded its CSPRNG correctly [@miningpq12], [@roca17].&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The &lt;a href=&quot;https://paragmali.com/blog/the-thirty-year-migration-ships-in-a-pip-install-how-post-qu/&quot; rel=&quot;noopener&quot;&gt;post-quantum migration timeline&lt;/a&gt;.&lt;/strong&gt; Key exchange is migrating; signatures are years behind, and harvest-now-decrypt-later keeps the pressure on [@cloudflare24], [@ir8547].&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Most of this list is someone else&apos;s research agenda. Two entries are not: the v1.5 &lt;em&gt;encryption&lt;/em&gt; long tail and weak key-generation entropy are the ones most likely to be sitting in your own stack right now, in an HSM integration or an embedded device you inherited. Audit those two first.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Enough theory. Here is the whole argument compressed into rules you can apply on Monday.&lt;/p&gt;
&lt;h2&gt;11. The Practical Guide and the Misuse Catalog&lt;/h2&gt;
&lt;p&gt;Every rule below is one named break from the catalog, turned inside out. If you remember nothing else, remember the decision tree.&lt;/p&gt;

flowchart TD
    START{&quot;What do you need?&quot;} --&amp;gt;|Confidentiality| ENC{&quot;Can you avoid RSA encryption?&quot;}
    START --&amp;gt;|Authenticity| SIG{&quot;New design or legacy mandate?&quot;}
    ENC --&amp;gt;|Yes| KEM[Use ECDH or hybrid X25519 plus ML-KEM-768]
    ENC --&amp;gt;|No, a standard forces RSA| OAEP[RSAES-OAEP, SHA-256, constant-time decode, at least 2048-bit]
    SIG --&amp;gt;|New design| PSS[RSASSA-PSS with SHA-256]
    SIG --&amp;gt;|Legacy mandate| V15[v1.5 signature, verify strictly, no e equals 3 shortcut]
    KEM --&amp;gt; KEYS[Keys: e equals 65537, CSPRNG primes, verify-before-release, blinding]
    OAEP --&amp;gt; KEYS
    PSS --&amp;gt; KEYS
    V15 --&amp;gt; KEYS
    KEYS --&amp;gt; PQ{&quot;Secret must survive past 2030?&quot;}
    PQ --&amp;gt;|Yes| HY[Deploy hybrid post-quantum now]
    PQ --&amp;gt;|No| DONE[Ship it]
&lt;p&gt;Spelled out as decision rules:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Encryption and key transport.&lt;/strong&gt; Prefer ECDH or a KEM, migrating to hybrid X25519 plus ML-KEM-768. If a standard forces RSA encryption, use RSAES-OAEP with SHA-256 and MGF1-SHA-256, a modulus of at least 2,048 bits, and a constant-time decoder with implicit rejection -- never v1.5 encryption [@rfc8017], [@sp80056b]. Never RSA-encrypt a payload; encapsulate a symmetric key and let an AEAD carry the data.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Signatures.&lt;/strong&gt; Use RSASSA-PSS for new designs [@pss96], [@fips1865]. Use v1.5 signatures only where mandated, and then verify strictly: full parse, re-encode and compare, reject trailing data, no $e = 3$ shortcut [@rfc8017].&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Keys and parameters.&lt;/strong&gt; A 2,048-bit floor, 3,072 or 4,096 bits for long-term secrets; $e = 65537$; independent CSPRNG-drawn primes per key; CRT with verify-before-release; base blinding [@sp80057], [@fips1865].&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Migration.&lt;/strong&gt; Deploy hybrid post-quantum key agreement now, and inventory every RSA usage against the IR 8547 2030 and 2035 clock [@ir8547].&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The same rules as a lookup table:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Task&lt;/th&gt;
&lt;th&gt;Use&lt;/th&gt;
&lt;th&gt;Key parameters&lt;/th&gt;
&lt;th&gt;Because (which break)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;Key transport, preferred&lt;/td&gt;
&lt;td&gt;ECDH or hybrid X25519 + ML-KEM-768&lt;/td&gt;
&lt;td&gt;Ephemeral keys&lt;/td&gt;
&lt;td&gt;Forward secrecy, no oracle, quantum hedge&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Key transport, if RSA forced&lt;/td&gt;
&lt;td&gt;RSAES-OAEP, constant-time decode&lt;/td&gt;
&lt;td&gt;SHA-256, MGF1-SHA-256, at least 2048-bit&lt;/td&gt;
&lt;td&gt;Bleichenbacher, Manger, Marvin&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Signature, new design&lt;/td&gt;
&lt;td&gt;RSASSA-PSS&lt;/td&gt;
&lt;td&gt;SHA-256, random salt&lt;/td&gt;
&lt;td&gt;v1.5 hand-wave, e equals 3 forgery&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Signature, legacy mandate&lt;/td&gt;
&lt;td&gt;v1.5, verified strictly&lt;/td&gt;
&lt;td&gt;Full parse, reject trailing bytes&lt;/td&gt;
&lt;td&gt;e equals 3 forgery, BERserk&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Key generation&lt;/td&gt;
&lt;td&gt;e equals 65537, CSPRNG primes&lt;/td&gt;
&lt;td&gt;2048 floor, 3072+ long-term&lt;/td&gt;
&lt;td&gt;Wiener, ROCA, Mining Ps and Qs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Private operation&lt;/td&gt;
&lt;td&gt;CRT verify-before-release, base blinding&lt;/td&gt;
&lt;td&gt;Recompute before output&lt;/td&gt;
&lt;td&gt;Bellcore fault, Kocher timing&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Long-term secrets&lt;/td&gt;
&lt;td&gt;Hybrid post-quantum now&lt;/td&gt;
&lt;td&gt;Inventory to IR 8547 clock&lt;/td&gt;
&lt;td&gt;Shor, harvest-now-decrypt-later&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Now the misuse catalog. Each antipattern maps to exactly one rule it violates -- the findings that recur in real code review:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;v1.5 encryption &quot;kept for compatibility.&quot;&lt;/strong&gt; The single most dangerous line in an RSA integration. Violates: retire v1.5 encryption.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Non-constant-time OAEP or v1.5 decode.&lt;/strong&gt; A decoder that branches or times differently on padding failure. Violates: constant-time, implicit-rejection decoding (Manger, Marvin).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Distinct decryption error messages, or branch timing.&lt;/strong&gt; Any observable difference between &quot;bad padding&quot; and &quot;other error.&quot; Violates: uniform, unobservable failure (Bleichenbacher).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Missing CRT verify-before-release.&lt;/strong&gt; Signing without recomputing the result first. Violates: verify before release (Bellcore fault).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Unblinded exponentiation.&lt;/strong&gt; A modexp whose time depends on the secret. Violates: base blinding (Kocher, Brumley-Boneh).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;An $e = 3$ shortcut in a verifier.&lt;/strong&gt; Checking the prefix without rejecting trailing data. Violates: verify strictly (e-equals-3 forgery, BERserk).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Textbook RSA copied from a tutorial.&lt;/strong&gt; The raw permutation on a message. Violates: never call the raw primitive.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;RSA-encrypting a large payload.&lt;/strong&gt; Treating RSA as a bulk cipher. Violates: encapsulate a key, never encrypt data.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Weak, shared, or reused-modulus keys.&lt;/strong&gt; Primes drawn from a cold CSPRNG or a structured library. Violates: seed the CSPRNG, draw primes uniformly (Mining Ps and Qs, ROCA).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;OAEP hash or label mismatch, or MGF1 silently defaulting to SHA-1.&lt;/strong&gt; A quiet interoperability and downgrade trap. Violates: pin the hash and MGF explicitly.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Treating a &quot;small&quot; side channel as unexploitable.&lt;/strong&gt; Marvin demolished that folklore -- a few microseconds recovered keys [@marvin23]. Violates: assume every observable leaks.&lt;/li&gt;
&lt;/ul&gt;

Grep for the dangerous entry points and confirm every RSA decryption path uses OAEP with a constant-time decoder:&lt;ul&gt;
&lt;li&gt;Go: search for &lt;code&gt;DecryptPKCS1v15&lt;/code&gt; and &lt;code&gt;EncryptPKCS1v15&lt;/code&gt;; move to &lt;code&gt;DecryptOAEP&lt;/code&gt; and &lt;code&gt;EncryptOAEP&lt;/code&gt;. The v1.5 decryptor is deprecated for the reason quoted earlier [@gorsa].&lt;/li&gt;
&lt;li&gt;Java: flag &lt;code&gt;Cipher.getInstance(&quot;RSA/ECB/PKCS1Padding&quot;)&lt;/code&gt;; require &lt;code&gt;RSA/ECB/OAEPWithSHA-256AndMGF1Padding&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;OpenSSL CLI: &lt;code&gt;openssl pkeyutl -encrypt -pubin -inkey pub.pem -pkeyopt rsa_padding_mode:oaep -pkeyopt rsa_oaep_md:sha256&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If a dependency genuinely still needs v1.5 decryption, confirm it runs a constant-time, implicit-rejection decoder -- OpenSSL 3.2 does so by default [@openssl32].
&lt;/p&gt;&lt;p&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; In practice, three violations dominate real audits: RSA v1.5 &lt;em&gt;encryption&lt;/em&gt; still enabled &quot;for a legacy client,&quot; a decryption path that is not verifiably constant-time, and RSA being used to encrypt bulk data instead of a symmetric key. Fix those three and you have closed most of the catalog [@marvin23], [@gorsa], [@openssl32].&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Read the whole failure catalog again as this checklist, and one pattern remains.&lt;/p&gt;
&lt;h2&gt;12. Trapdoor, Not Cryptosystem&lt;/h2&gt;
&lt;p&gt;Return to where we began. The researchers who signed with facebook.com&apos;s key never factored its modulus, and neither did anyone in the twenty-five years of breaks between Bleichenbacher and Marvin. The factoring problem stood untouched the whole time. What fell, every single time, was something else: the decryptor&apos;s error handling, the signer&apos;s fault behaviour, the implementation&apos;s clock, or the generator&apos;s entropy. The attacker&apos;s real move is never to solve the hard math -- it is to turn the receiver&apos;s own reaction into the private-key operation.&lt;/p&gt;

The attacker does not factor the modulus. They turn the decryptor&apos;s reaction into the private-key operation -- and &quot;done right&quot; is the discipline of leaving that reaction with nothing to say.
&lt;p&gt;That is why &quot;RSA done right&quot; is a stack and not a setting. The right scheme, OAEP to encrypt and PSS for new signatures, does two of the three jobs. The right parameters -- a 2,048-bit floor, $e = 65537$, primes from a real CSPRNG -- keep the trapdoor strong. And the constant-time, fault-checked, blinded implementation does the third job, the one the field kept forgetting: it leaves no observable behaviour that answers the attacker&apos;s one question.&lt;/p&gt;
&lt;p&gt;Every deployed disaster in this article is a stack with exactly one layer missing. Textbook RSA is broken not because the math is weak but because it is &lt;em&gt;only&lt;/em&gt; the trapdoor, with none of the layers that make a trapdoor into a cryptosystem.&lt;/p&gt;
&lt;p&gt;The forward horizon makes the discipline sharper, not softer. RSA done right is stable against every known classical attack, and it still has an expiration date, because the one gap RSA can never close is the quantum one. The destination is not a better RSA. It is KEM-DEM composition and post-quantum algorithms -- constructions where the public-key operation carries only a uniform random key, and where Shor has no polynomial-time shortcut to wait for.&lt;/p&gt;
&lt;p&gt;So ask the diagnostic question one final time, now that it is answerable. When a ciphertext or signature it did not create arrives, what does the receiver reveal about it -- through its answer, its timing, or its faults? Done right, the answer is nothing at all.&lt;/p&gt;


No, but the word &quot;RSA&quot; hides three different answers. Textbook RSA is broken. PKCS#1 v1.5 *encryption* is dangerous and should be retired [@marvin23]. Done-right RSA -- OAEP or PSS, constant-time, at least 2,048 bits, public exponent 65537 -- is fine against every known classical attack, right up until a large quantum computer exists, which is why you should be planning migration in parallel [@ir8547].


No. Encapsulate a fresh random symmetric key with the public key and let an authenticated cipher encrypt the file. RSA-OAEP at 2,048 bits carries only about 190 bytes of plaintext anyway, so it was never meant for bulk data -- it moves keys, not files [@sp80056b].


No. OAEP fixes the *scheme*, not the decoder. James Manger showed that a non-constant-time OAEP decoder re-opens the very same padding oracle, and more cheaply than the original Bleichenbacher attack [@manger01]. The rule is &quot;OAEP AND constant-time decoding,&quot; never OAEP alone.


Not as a scheme -- there is no known attack against the RSASSA-PKCS1-v1_5 signature construction itself [@rfc8017]. What breaks is lax *verifiers*: the 2006 e-equals-3 forgery and the 2014 BERserk bug both forged signatures past sloppy verification, not by breaking the scheme [@cve2006], [@cve2014]. Verify strictly -- parse the entire structure and reject any trailing data.


Only with correct padding and a strict verifier, and it is not worth the risk. A small exponent has repeatedly enabled Hastad&apos;s broadcast attack, Coppersmith&apos;s low-exponent attacks, and signature forgeries [@hastad88], [@coppersmith97]. Use 65537: it is fast and dodges every low-exponent trap.


Yes for today -- a 2,048-bit modulus gives about 112 bits of security [@sp80057]. Use 3,072 bits or more for anything that must stay secret long-term, and start post-quantum planning: NIST deprecates 112-bit RSA after 2030 and disallows it after 2035 [@ir8547].


For most new work, yes. ECDH and Ed25519 give smaller, faster keys with the forward secrecy RSA key transport never had [@rfc8446], and hybrid X25519 plus ML-KEM-768 is already carrying real TLS traffic for the quantum transition [@cloudflare24]. RSA&apos;s destination is retirement, not repair.

&lt;p&gt;&amp;lt;StudyGuide slug=&quot;rsa-done-right-oaep-pss-bleichenbacher&quot; keyTerms={[
  { term: &quot;Trapdoor permutation&quot;, definition: &quot;A one-way function with a secret shortcut. RSA maps x to x-to-the-e modulo N, invertible only by whoever knows the factorization of N. It is a primitive, not a complete cryptosystem.&quot; },
  { term: &quot;Malleability&quot;, definition: &quot;RSA&apos;s multiplicative homomorphism: multiplying a ciphertext by a chosen factor predictably scales the plaintext, letting an attacker transform messages without decrypting. It is the lever behind the padding-oracle attacks.&quot; },
  { term: &quot;Padding oracle&quot;, definition: &quot;Any observable behaviour (an error, a timing difference, or a fault) that reveals whether a decrypted ciphertext had valid padding, turning a validity check into a decryption oracle.&quot; },
  { term: &quot;RSAES-OAEP&quot;, definition: &quot;A randomized, all-or-nothing encryption padding applied before the RSA permutation, giving chosen-ciphertext security in the random-oracle model, provided the decoder runs in constant time.&quot; },
  { term: &quot;RSASSA-PSS&quot;, definition: &quot;A randomized, salted RSA signature encoding with a tight security reduction to the RSA problem, the provable choice for new signature designs.&quot; },
  { term: &quot;Implicit rejection&quot;, definition: &quot;Returning a deterministic pseudo-random value on a padding failure instead of a distinguishable error, so success and failure are unobservable through content, error type, or timing.&quot; },
  { term: &quot;CRT in RSA&quot;, definition: &quot;Computing the private operation modulo p and modulo q separately for a roughly fourfold speedup, at the cost of a fault-attack surface unless the result is verified before release.&quot; },
  { term: &quot;KEM-DEM&quot;, definition: &quot;Transporting a random symmetric key with the public key, then encrypting the data with that key under an authenticated cipher, so no chosen structured plaintext exists for an oracle to grade.&quot; },
  { term: &quot;IND-CCA2&quot;, definition: &quot;The strongest standard security goal for encryption: even with access to a decryption oracle, an attacker cannot tell which plaintext a ciphertext hides. Padding oracles violate it.&quot; }
]} questions={[
  { q: &quot;Why is textbook RSA not a cryptosystem?&quot;, a: &quot;It is deterministic, malleable, and structurally leaky for small exponents. It needs padding that adds randomness, adds checkable redundancy, and leaks nothing about whether the check passed.&quot; },
  { q: &quot;What single bit did every attack from Bleichenbacher to Marvin leak?&quot;, a: &quot;Whether the padding was valid. Only the channel changed, from an error message to a cross-protocol zombie to a TCP quirk to pure timing.&quot; },
  { q: &quot;Why is OAEP necessary but not sufficient?&quot;, a: &quot;OAEP secures the scheme, but Manger showed that a non-constant-time decoder re-opens the same oracle. Security is a property of the scheme and its implementation together.&quot; },
  { q: &quot;What is the one split you must never blur?&quot;, a: &quot;v1.5 encryption is dangerous and should be retired, while v1.5 signatures are unbroken as a scheme and still dominant. Verify the signatures strictly.&quot; },
  { q: &quot;Why does done-right RSA still have an expiration date?&quot;, a: &quot;There is no proof RSA is secure, and Shor&apos;s algorithm factors in polynomial time on a quantum computer, so the destination is KEM-DEM plus post-quantum cryptography.&quot; }
]} /&amp;gt;&lt;/p&gt;
</content:encoded><category>rsa</category><category>oaep</category><category>rsa-pss</category><category>padding-oracle</category><category>bleichenbacher</category><category>post-quantum-crypto</category><category>cryptography</category><category>applied-cryptography</category><author>noreply@paragmali.com (Parag Mali)</author></item><item><title>They Read Your Plaintext Without Breaking Your Cipher: A Field Guide to Padding Oracles</title><link>https://paragmali.com/blog/they-read-your-plaintext-without-breaking-your-cipher-a-fiel/</link><guid isPermaLink="true">https://paragmali.com/blog/they-read-your-plaintext-without-breaking-your-cipher-a-fiel/</guid><description>A padding oracle reads your plaintext without touching your key. Why CBC, Vaudenay, Lucky13, and POODLE are one bug -- and why Encrypt-then-MAC ends it.</description><pubDate>Wed, 08 Jul 2026 20:09:31 GMT</pubDate><content:encoded>
A padding oracle lets an attacker read your plaintext without ever touching your key -- and it is not a flaw in AES, in CBC, or even in PKCS#7. It is what you get whenever a receiver reveals *whether an attacker-chosen ciphertext decrypted to well-formed plaintext* before it has proven that ciphertext authentic. That one leaked bit (&quot;was the padding valid?&quot;), plus CBC&apos;s malleability, recovers plaintext one byte at a time -- about 128 guesses per byte -- as Serge Vaudenay showed in 2002 and Juliano Rizzo and Thai Duong weaponized against ASP.NET in 2010.&lt;p&gt;This field guide follows the &lt;em&gt;same&lt;/em&gt; bug leaking through progressively quieter channels: a loud error (Vaudenay), coarse timing (Canvel), statistical timing under unified errors (Lucky Thirteen), SSL 3.0&apos;s unchecked padding (POODLE), and the oracle re-created by Lucky Thirteen&apos;s own fix (CVE-2016-2107). Every point fix lost. The single structural cure is to invert the order: &lt;strong&gt;authenticate before you decrypt-and-unpad.&lt;/strong&gt; Encrypt-then-MAC proves it attains IND-CCA plus INT-CTXT; AEAD, mandated by TLS 1.3, ships it with no knob to misset. The article ends with ranked decision rules and a design-review checklist for 2026.
&lt;/p&gt;&lt;p&gt;&lt;/p&gt;
&lt;h2&gt;1. The Break That Never Touched the Key&lt;/h2&gt;
&lt;p&gt;In September 2010, two researchers pointed a few thousand malformed requests at an ordinary ASP.NET application and walked away with its authentication tickets and its &lt;code&gt;web.config&lt;/code&gt; -- the site&apos;s master secrets -- without ever attacking its AES-256 encryption or going near the key [@rizzo_duong_woot2010]. It had done nothing more than return one error when a decrypted request had bad padding and a different error when the padding was fine but the data was not. That one-bit difference is a decryption machine: the attackers never broke the cipher, they turned the server&apos;s error handling into the decryption function.&lt;/p&gt;
&lt;p&gt;Sit with the paradox, because the rest of this article lives inside it. AES-256 was, and is, intact. Nobody factored anything, guessed a key, or exploited a weakness in the block cipher&apos;s rounds. The plaintext walked out the door one byte at a time purely because the server answered a question it should never have been willing to answer: &lt;em&gt;was this ciphertext, which I did not create, well-formed after I decrypted it?&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Juliano Rizzo and Thai Duong automated that question against &lt;code&gt;WebResource.axd&lt;/code&gt; and &lt;code&gt;ScriptResource.axd&lt;/code&gt; handlers and recovered Forms-authentication tickets and server files. The United States National Vulnerability Database catalogs it as CVE-2010-3332, the &quot;ASP.NET Padding Oracle Vulnerability,&quot; and Microsoft shipped an out-of-band patch to close it [@cve_2010_3332]. This was not a lab curiosity. It was, at the time, the largest deployed instance of a bug that had been sitting in plain sight for eight years.&lt;/p&gt;

A padding oracle is an attack class, not a cipher weakness. A receiver decrypts an attacker-chosen ciphertext, checks whether the recovered plaintext has valid padding, and reveals the verdict -- through an error message, a timing difference, or any observable behavior. That single accept/reject bit, repeated, becomes a plaintext-recovery oracle that never attacks the underlying cipher or key.
&lt;p&gt;The reframe is worth stating in the sharpest possible terms, because once you hold it you will see this bug everywhere.&lt;/p&gt;

The attacker never attacks the cipher. They turn the receiver&apos;s error handling into the decryption function -- &quot;decrypt, then validate&quot; becomes &quot;decrypt, then confess.&quot;
&lt;p&gt;This is Part 6 of a field guide for protocol designers. It assumes the malleability of CBC mode from Part 5 (the lever the attack pulls) and the &lt;a href=&quot;https://paragmali.com/blog/secure-against-whom-the-security-definitions-every-protocol-/&quot; rel=&quot;noopener&quot;&gt;chosen-ciphertext adversary&lt;/a&gt; from Part 1 (this &lt;em&gt;is&lt;/em&gt; that adversary, deployed and winning). By the end you will carry one diagnostic sentence and drop every named break in this article -- Vaudenay, Lucky Thirteen, POODLE, and tomorrow&apos;s application-layer oracle -- onto it on sight: &lt;em&gt;when a ciphertext you did not create arrives, what does the receiver reveal about it before it has proven the ciphertext authentic?&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;If AES was intact and the key was never touched, then the machine that recovered the plaintext was built entirely out of the server&apos;s own responses. That idea did not begin with CBC, or even with symmetric cryptography. To see the atom clearly, go back to the first time anyone realized that a receiver&apos;s verdict on validity is itself a decryption function.&lt;/p&gt;
&lt;h2&gt;2. A Validity Check Is a Decryption Oracle&lt;/h2&gt;
&lt;p&gt;The year is 1998, the place is Bell Labs, and Daniel Bleichenbacher is staring at an SSL server that is far too honest. When the server receives an RSA-encrypted message, it decrypts and checks whether the result has the structure that the PKCS #1 v1.5 standard demands. If the structure is wrong, it says so. Bleichenbacher realized that this single yes/no answer -- &lt;em&gt;does the decrypted message have valid PKCS #1 formatting?&lt;/em&gt; -- is enough to recover the plaintext.&lt;/p&gt;
&lt;p&gt;With roughly $2^{20}$ adaptively chosen queries, he could extract an RSA-protected session key without factoring the modulus and without ever learning the private key [@bleichenbacher1998]. The server&apos;s &quot;is this well-formed?&quot; answer &lt;em&gt;was&lt;/em&gt; the attacker&apos;s decryption function. This is the seed of everything that follows.&lt;/p&gt;
&lt;p&gt;It is also the moment to nail down a boundary that expert readers will check for. Bleichenbacher&apos;s attack is an &lt;strong&gt;RSA PKCS #1 v1.5&lt;/strong&gt; oracle -- a different primitive with different arithmetic. It is the conceptual ancestor of the CBC padding-oracle class &quot;in spirit,&quot; but it is never itself a CBC padding oracle [@bleichenbacher1998].Calling Bleichenbacher &quot;the first CBC padding oracle&quot; is a common and consequential error. His target is RSA public-key encryption; the CBC class is symmetric. The shared idea -- a structural validity verdict on attacker-chosen ciphertext leaks the plaintext -- is what makes him the ancestor, not the mechanism. The distinction matters because the family tree has two branches, and confusing them muddles the fix.&lt;/p&gt;

In an adaptive chosen-ciphertext attack, the adversary submits ciphertexts of its choosing and learns something from the receiver&apos;s reaction to each one. A scheme is IND-CCA2 secure only if those reactions leak nothing that helps distinguish or recover plaintext. A padding oracle is precisely a receiver whose reaction leaks -- so it is a live, deployed IND-CCA2 break, the security notion this series defines in Part 1.
&lt;p&gt;The whole subject lives inside that definition. If a receiver&apos;s response to a ciphertext it did not create tells the attacker anything, the game is already lost -- the only question is how expensive the win is.&lt;/p&gt;

Bleichenbacher&apos;s oracle never died; it changed clothes. **DROWN** (2016) revived it by using a server&apos;s SSLv2 support to attack modern TLS [@drown_attack2016]. **ROBOT** (2017 to 2018) found the same PKCS #1 v1.5 oracle live in load balancers and TLS stacks from major vendors, nearly two decades on [@robot_2018]. And the **Marvin Attack** (2023) showed the timing variant still breaks RSA implementations &quot;previously thought immune&quot; [@marvin_attack2023]. All three are RSA-side breaks -- a different primitive from the CBC story told here -- and they appear only as this signpost so nobody mistakes them for CBC oracles. The lesson each one re-teaches is the same: a validity verdict on attacker-chosen input is a decryption oracle, whatever the cipher.
&lt;p&gt;Four years after Bleichenbacher, Serge Vaudenay carried the idea across the aisle from public-key to symmetric cryptography. In &quot;Security Flaws Induced by CBC Padding,&quot; presented at EUROCRYPT 2002, he showed that a receiver which decrypts a CBC ciphertext and reveals whether the padding is valid becomes a plaintext-recovery oracle against SSL, IPSEC, and WTLS [@vaudenay2002]. This is the founding result of the class -- the moment &quot;confidentiality without integrity&quot; was shown to be, in the general case, a decryption oracle waiting to be asked.&lt;/p&gt;
&lt;p&gt;The object it manipulates had arrived on schedule: PKCS #7 padding, the append-&lt;code&gt;n&lt;/code&gt;-bytes-each-equal-to-&lt;code&gt;n&lt;/code&gt; scheme, was standardized as RFC 2315 in the same era, so the manipulable surface and the attack template appeared together [@rfc2315]. Vaudenay&apos;s procedure is documented in cryptography&apos;s standard texts as the canonical illustration of chosen-ciphertext insecurity [@katz_lindell_imc].&lt;/p&gt;

timeline
    title Padding oracles, 1998 to 2022
    1998 : Bleichenbacher RSA PKCS number 1 oracle
    2000 : Bellare-Namprempre composition theorem
    2002 : Vaudenay CBC padding oracle
    2003 : Canvel timing oracle vs TLS
    2010 : ASP.NET break by Rizzo and Duong
    2013 : Lucky Thirteen statistical timing
    2014 : POODLE and RFC 7366 Encrypt-then-MAC
    2016 : CVE-2016-2107, the fix re-creates the oracle
    2018 : TLS 1.3 removes CBC, mandates AEAD
    2022 : BCP 195 recommends against CBC
&lt;p&gt;Vaudenay&apos;s real contribution was noticing that CBC makes the symmetric version &lt;em&gt;cleaner&lt;/em&gt; than Bleichenbacher&apos;s RSA original: flip a byte, watch the verdict, recover a byte. To believe that -- and to earn the right to use it on every later break -- you have to see the arithmetic. It is simpler than it sounds, and once you see it you cannot un-see it in any decrypt-then-check system you review.&lt;/p&gt;
&lt;h2&gt;3. The Atom: Vaudenay&apos;s Oracle, Worked Byte by Byte&lt;/h2&gt;
&lt;p&gt;Everything in this article is one move, performed once here and then reused by every named break with only the &lt;em&gt;channel&lt;/em&gt; changed. Derive it in full and the rest of the piece can move fast. There are three steps: understand the padding, understand the lever, then watch the recovery.&lt;/p&gt;
&lt;h3&gt;Step one: three padding formats, disambiguated once&lt;/h3&gt;
&lt;p&gt;Block ciphers operate on whole blocks, so a message that does not fill the last block must be padded, and the padding must be self-describing so the receiver can strip it. PKCS #7 is the canonical scheme.&lt;/p&gt;

To pad a message to a block boundary, append `n` bytes, each equal to the value `n`. Need three bytes of padding? Append `03 03 03`. Need one? Append `01`. If the message is already block-aligned, append a *full extra block* of padding (for a 16-byte AES block, sixteen bytes of `10`). To unpad, read the final byte `n`, verify that the last `n` bytes all equal `n`, and strip them. Standardized in RFC 2315 [@rfc2315].
&lt;p&gt;Here is the trap that catches expert readers, so disambiguate it now and never again: not every named attack operates on literal PKCS #7 bytes. Three formats matter, and mitigations for one do not automatically cover another.&lt;/p&gt;
&lt;p&gt;PKCS #7 (RFC 2315) requires every pad byte to equal the pad length [@rfc2315]. TLS 1.0 through 1.2 use a PKCS #7-&lt;em&gt;like&lt;/em&gt; scheme where every pad byte equals &lt;code&gt;length - 1&lt;/code&gt;, and -- this detail will return with Lucky Thirteen -- the record is MAC&apos;d, &lt;em&gt;then&lt;/em&gt; padded, &lt;em&gt;then&lt;/em&gt; encrypted [@rfc5246]. SSL 3.0 is the dangerous outlier: its pad bytes are &lt;em&gt;arbitrary&lt;/em&gt;, and only the final length byte is constrained, so there is effectively no padding content to check at all [@rfc6101].&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Padding format&lt;/th&gt;
&lt;th&gt;Pad-byte rule&lt;/th&gt;
&lt;th&gt;What the receiver checks&lt;/th&gt;
&lt;th&gt;Break that targets it&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;PKCS #7 (RFC 2315)&lt;/td&gt;
&lt;td&gt;Append &lt;code&gt;n&lt;/code&gt; bytes each equal to &lt;code&gt;n&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Length byte and every pad byte&lt;/td&gt;
&lt;td&gt;Vaudenay&apos;s canonical oracle&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TLS 1.0 to 1.2 (RFC 5246)&lt;/td&gt;
&lt;td&gt;Every pad byte equals &lt;code&gt;length - 1&lt;/code&gt;; MAC then pad then encrypt&lt;/td&gt;
&lt;td&gt;Length and pad bytes, after decrypt&lt;/td&gt;
&lt;td&gt;Lucky Thirteen&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SSL 3.0 (RFC 6101)&lt;/td&gt;
&lt;td&gt;Pad bytes arbitrary; only the final length byte constrained&lt;/td&gt;
&lt;td&gt;Length byte only; pad bytes unchecked&lt;/td&gt;
&lt;td&gt;POODLE&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;The DES-era ancestor is PKCS #5, re-published as RFC 8018, which is the same scheme fixed at an 8-byte block; PKCS #7 generalizes it to any block size up to 255 [@rfc8018]. The two names describe one idea, which is why you see both in old code.&lt;/p&gt;
&lt;p&gt;That single table is why &quot;just make the padding check uniform&quot; was never a complete fix -- it says nothing about SSL 3.0, where there is no pad content to make uniform.&lt;/p&gt;
&lt;h3&gt;Step two: CBC malleability, the lever&lt;/h3&gt;

In CBC decryption, each plaintext block is $P_i = D_k(C_i) \oplus C_{i-1}$: decrypt the current ciphertext block with the block cipher, then XOR the previous ciphertext block. Because that XOR is the last step, an attacker who flips a bit in $C_{i-1}$ deterministically flips the *same* bit in $P_i$ -- steering the plaintext without any knowledge of the key. Part 5 derives this in full; here it is the lever the oracle pulls.

flowchart TD
    Ci[&quot;Target ciphertext block Ct&quot;] --&amp;gt; Dk[&quot;Block cipher decryption Dk&quot;]
    Dk --&amp;gt; Inter[&quot;Intermediate block, equals Dk of Ct&quot;]
    Cprev[&quot;Previous ciphertext block, attacker controls it&quot;] --&amp;gt; XORnode((&quot;XOR&quot;))
    Inter --&amp;gt; XORnode
    XORnode --&amp;gt; Pt[&quot;Recovered plaintext block Pt&quot;]
    Attacker[&quot;Attacker flips byte j of the previous block&quot;] -.-&amp;gt; Cprev
    Attacker -.-&amp;gt; Result[&quot;Byte j of Pt flips by the same amount, no key needed&quot;]
&lt;p&gt;Hold the two pieces together: the receiver will tell you whether a decrypted block has valid padding, and you can steer any byte of that decrypted block by editing the block in front of it. Those two facts are a decryption oracle.&lt;/p&gt;
&lt;h3&gt;Step three: the recovery, last byte first&lt;/h3&gt;
&lt;p&gt;Take a target ciphertext block $C_t$ whose plaintext you want. Prepend a block $C&apos;$ that you fully control, and ask the receiver to decrypt the pair. The receiver computes an intermediate block $I = D_k(C_t)$ and returns the plaintext $P = I \oplus C&apos;$, then checks its padding. You do not know $I$, but you can search for it one byte at a time.&lt;/p&gt;
&lt;p&gt;Fix your attention on the last byte. Vary $C&apos;$&apos;s last byte over all 256 possible values and watch the verdict. For exactly one value, the decrypted last byte becomes &lt;code&gt;0x01&lt;/code&gt;, which is valid single-byte padding, and the receiver accepts. At that instant you know that $I[\text{last}] \oplus C&apos;[\text{last}] = \texttt{0x01}$, so the intermediate byte is $I[\text{last}] = C&apos;[\text{last}] \oplus \texttt{0x01}$. XOR that against the &lt;em&gt;real&lt;/em&gt; previous ciphertext byte and you have recovered a true plaintext byte -- and AES was never touched.&lt;/p&gt;
&lt;p&gt;One honest wrinkle hides inside &quot;about 128 guesses.&quot; A guess can occasionally produce valid &lt;em&gt;longer&lt;/em&gt; padding by luck -- for instance if the plaintext already ended in &lt;code&gt;0x02&lt;/code&gt;, a crafted byte could yield &lt;code&gt;02 02&lt;/code&gt;. So the careful attacker perturbs the second-to-last byte to confirm the intended &lt;code&gt;01&lt;/code&gt; interpretation. That rare double-check is why the average is ~128 rather than a clean 128, and why worst-case is 256.&lt;/p&gt;
&lt;p&gt;To peel the next byte, target two-byte padding: set the known last byte to decrypt to &lt;code&gt;0x02&lt;/code&gt; (you can, because you now know its intermediate), and brute-force the second-to-last byte until the receiver sees &lt;code&gt;02 02&lt;/code&gt;. March inward -- &lt;code&gt;03 03 03&lt;/code&gt;, then &lt;code&gt;04 04 04 04&lt;/code&gt; -- and a full block falls. The cost is about &lt;strong&gt;128 guesses per byte on average and 256 in the worst case&lt;/strong&gt; [@vaudenay2002].&lt;/p&gt;

sequenceDiagram
    participant A as Attacker
    participant O as Oracle receiver
    Note over A,O: Goal, learn the last byte of target block Ct
    A-&amp;gt;&amp;gt;O: Send chosen block Cprime, then Ct
    O-&amp;gt;&amp;gt;O: Decrypt, inspect last-byte padding
    O--&amp;gt;&amp;gt;A: invalid padding
    Note over A: Increment last byte of Cprime, retry
    A-&amp;gt;&amp;gt;O: The value making the last plaintext byte 0x01
    O-&amp;gt;&amp;gt;O: Padding parses as one valid byte
    O--&amp;gt;&amp;gt;A: valid padding
    Note over A,O: Intermediate byte is that guess XOR 0x01
    Note over A: True plaintext byte is intermediate XOR real previous byte
&lt;p&gt;Run the atom yourself. The simulation below models the oracle over a fixed intermediate block -- no real cipher, so you can watch the one-in-256 accept that leaks a byte.&lt;/p&gt;
&lt;p&gt;{`
// A toy padding oracle over a fixed 16-byte &quot;intermediate&quot; block.
// No real crypto: we model I = D_k(C_t) as a known array so you can watch
// the 1-in-256 accept that leaks one plaintext byte.
const BLOCK = 16;&lt;/p&gt;
&lt;p&gt;// Pretend these are the secret intermediate bytes I = D_k(C_t).
const intermediate = [0x8a,0x2f,0x11,0x77,0x4c,0x93,0xe0,0x05,
                      0xbb,0x6d,0x19,0xa4,0x3c,0xf8,0x52,0x7e];
// The real previous-ciphertext block (known to the attacker).
const realPrev = [0x01,0x23,0x45,0x67,0x89,0xab,0xcd,0xef,
                  0xfe,0xdc,0xba,0x98,0x76,0x54,0x32,0x10];&lt;/p&gt;
&lt;p&gt;// Oracle: is the last decrypted byte valid 0x01 padding?
function oracle(cprime) {
  const lastPlain = intermediate[BLOCK-1] ^ cprime[BLOCK-1];
  return lastPlain === 0x01;   // the single leaked bit
}&lt;/p&gt;
&lt;p&gt;// Attack: brute-force the last byte of a controlled block.
let guesses = 0;
const cprime = new Array(BLOCK).fill(0);
for (let g = 0; g &amp;lt; 256; g++) {
  guesses++;
  cprime[BLOCK-1] = g;
  if (oracle(cprime)) {
    const intByte = g ^ 0x01;                    // I[last] = guess XOR 0x01
    const plainByte = intByte ^ realPrev[BLOCK-1];
    console.log(&quot;Accepted after &quot; + guesses + &quot; guesses&quot;);
    console.log(&quot;Recovered intermediate byte: 0x&quot; + intByte.toString(16));
    console.log(&quot;Recovered plaintext byte:    0x&quot; + plainByte.toString(16));
    break;
  }
}
`}&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key idea:&lt;/strong&gt; One reliable accept/reject bit on attacker-chosen ciphertext, plus CBC&apos;s malleability, is a complete decryption function -- about 128 guesses per byte, and the key is never touched. Every named break in the rest of this article is that same atom, with only the channel that carries the bit changed.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That is Aha number one, and it dismantles the mental model most engineers start with. AES-256&apos;s strength was never the variable. The variable was what the receiver was willing to say about a ciphertext it had not authenticated. Before moving on, one point of vocabulary, because it changes how you diagnose the bug.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; A padding oracle is an &lt;em&gt;attack class&lt;/em&gt; -- a side channel against a &lt;em&gt;construction&lt;/em&gt; (CBC-mode encryption composed with a MAC), not a weakness in a primitive. The block cipher (AES) and the MAC (HMAC) are the primitives, and they are doing exactly what they promise. The oracle lives in how they are composed and in what the receiver reveals. Say &quot;attack class&quot; and &quot;construction,&quot; never &quot;the padding-oracle primitive.&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The atom needs exactly one thing from the receiver: a distinguishable answer to &quot;was the padding valid?&quot; Vaudenay read that answer off a loud error message. The next fifteen years are the story of the field muffling that answer, one channel at a time -- and the same bit escaping through the next-quietest one every single time.&lt;/p&gt;
&lt;h2&gt;4. One Distinguisher, Four Quieter Channels&lt;/h2&gt;
&lt;p&gt;The atom never changes. What changes is &lt;em&gt;how the accept/reject bit is observed&lt;/em&gt; -- and each time the field silences one channel, the bit re-emerges through a quieter one. Here is the entire failure catalog as one descending staircase, with POODLE branching off to the side.&lt;/p&gt;

flowchart TD
    A[&quot;Loud padding-error message (Vaudenay, ASP.NET)&quot;] --&amp;gt;|&quot;Unify the alert&quot;| B[&quot;Coarse timing gap (Canvel)&quot;]
    B --&amp;gt;|&quot;Compute the MAC anyway&quot;| C[&quot;Statistical timing (Lucky Thirteen)&quot;]
    C --&amp;gt;|&quot;Constant-time AES-NI fast path&quot;| D[&quot;Fix re-creates the oracle (CVE-2016-2107)&quot;]
    D --&amp;gt;|&quot;Verify authenticity first&quot;| F[&quot;Encrypt-then-MAC and AEAD, no verdict to leak&quot;]
    P[&quot;Unchecked padding by spec (POODLE, SSL 3.0)&quot;] --&amp;gt;|&quot;Retire SSL 3.0 and CBC&quot;| F
&lt;p&gt;&lt;strong&gt;Channel one -- the loud error message (Vaudenay 2002, ASP.NET 2010).&lt;/strong&gt; The first receivers simply returned a distinct &quot;padding error,&quot; trivially observable. Vaudenay read it straight off the response, and eight years later Rizzo and Duong read it off differing HTTP responses from ASP.NET applications, recovering authentication tickets and &lt;code&gt;web.config&lt;/code&gt; at scale [@rizzo_duong_woot2010] [@cve_2010_3332]. The obvious countermeasure: return one indistinguishable error for both padding failures and data failures. That closes the loudest channel and opens the next.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Channel two -- coarse timing (Canvel et al. 2003).&lt;/strong&gt; Once OpenSSL returned the &lt;em&gt;same&lt;/em&gt; &lt;code&gt;bad_record_mac&lt;/code&gt; alert for padding and MAC failures, Brice Canvel, Alain Hiltgen, Serge Vaudenay, and Martin Vuagnoux showed the verdict still leaked through &lt;em&gt;when&lt;/em&gt; the alert arrived: a receiver that accepted the padding went on to compute the MAC, while one that rejected padding could bail early, and the resulting millisecond gap reconstituted the oracle [@canvel2003]. They intercepted a password over a live OpenSSL TLS channel. This is the first concrete demonstration of the pattern that organizes the whole subject. The standards response, TLS 1.1 (RFC 4346), mandated uniform alert handling and explicit per-record initialization vectors [@rfc4346]. The countermeasure: make the decryption path constant-time -- compute the MAC even on padding failure so both paths take equal time.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Channel three -- statistical timing, Lucky Thirteen (AlFardan and Paterson, 2013).&lt;/strong&gt; This is the break that proved constant-time is &lt;em&gt;the&lt;/em&gt; hard problem, and it is worth binding tightly to the thesis, because it is not merely &quot;a timing attack.&quot; TLS uses MAC-then-encrypt with a specific receiver order: decrypt, &lt;em&gt;strip the padding&lt;/em&gt;, then run HMAC over what remains. The trouble is that &quot;what remains&quot; has a length that depends on how many bytes the receiver treated as padding -- and HMAC&apos;s running time depends on that length, because the hash compression function runs one extra time at certain length thresholds. So the attacker&apos;s &lt;em&gt;guess about the padding&lt;/em&gt; changes the MAC-computation time by a few tens of CPU cycles [@lucky13_2013].&lt;/p&gt;

A comparison (or, more broadly, a code path) is constant-time when its running time does not depend on secret data or on how far into the data a difference occurs. A tag check that returns early on the first mismatched byte leaks *where* the mismatch is; a constant-time check XORs all bytes and inspects the accumulated difference once. Lucky Thirteen forced constant-time unpadding-and-MAC on the whole industry, and CVE-2016-2107 shows how easily a hand-written fast path reintroduces the oracle -- there, through a missing length check that produced a distinguishable alert rather than a timing gap.
&lt;p&gt;Nadhem AlFardan and Kenneth Paterson amplified that sub-microsecond difference statistically across the network, recovering a full plaintext block in about $2^{23}$ TLS sessions, dropping to roughly $2^{13}$ sessions per byte with favorable positioning, across OpenSSL, GnuTLS, PolarSSL, and more (CVE-2013-0169) [@lucky13_2013] [@cve_2013_0169]. Bind it to the ordering: MAC-then-encrypt &lt;em&gt;forces&lt;/em&gt; a length-dependent MAC, so the timing leak is a property of the order of operations, not a stray implementation slip.The name &quot;Lucky Thirteen&quot; comes from the 13 bytes TLS feeds into the HMAC before the record body: a 5-byte TLS record header plus an 8-byte sequence number. Their alignment against the 64-byte hash-compression boundary is exactly what opens the timing gap. A number that sounds whimsical is really a byte-counting accident. The next countermeasure was obvious and genuinely hard: make the MAC and the unpadding constant-time.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Channel four -- unchecked structure, POODLE (Moller, Duong, and Kotowicz 2014).&lt;/strong&gt; Here the staircase forks into a parallel branch. SSL 3.0&apos;s pad bytes are &lt;em&gt;arbitrary by specification&lt;/em&gt; -- only the final length byte is constrained -- so there is no padding check to harden, no message to unify, no timing to flatten [@rfc6101]. An attacker who maneuvers a target byte into the last position of a block learns it from &lt;em&gt;MAC pass or fail alone&lt;/em&gt;, needing neither an error message nor a timing measurement, at an expected cost of 256 SSL 3.0 requests per byte [@poodle_writeup] [@cve_2014_3566]. The name expands to Padding Oracle On Downgraded Legacy Encryption, and this is where a persistent misconception must be corrected.POODLE&apos;s downgrade is the &lt;em&gt;enabler&lt;/em&gt;, not the bug. The downgrade only forces a modern client back onto vulnerable SSL 3.0; the vulnerability is the unchecked pad. The proof is TLS-POODLE (CVE-2014-8730), which hit TLS stacks that copied SSL 3.0&apos;s lax padding check and works with &lt;em&gt;no downgrade at all&lt;/em&gt; [@cve_2014_8730]. Frame POODLE as a padding oracle first, a downgrade second. Google&apos;s write-up and the accompanying announcement documented the mechanism and effort in full [@poodle_writeup] [@google_poodle_blog]. Because there is no check to make uniform, the only fix is to remove SSL 3.0, and CBC, from the wire entirely.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Channel five -- the oracle the fix re-created (CVE-2016-2107, 2016).&lt;/strong&gt; This is the single clearest proof of the thesis. OpenSSL&apos;s AES-NI assembly fast path, written to make the Lucky Thirteen countermeasure constant-time, mishandled a length check -- and re-opened a padding oracle. It was found with the TLS-Attacker fuzzing methodology [@somorovsky_fuzzing2016], and the National Vulnerability Database records the cause in words worth quoting exactly.&lt;/p&gt;

This vulnerability [CVE-2016-2107] exists because of an incorrect fix for CVE-2013-0169.
&lt;p&gt;The constant-time patch for Lucky Thirteen re-created the very oracle it was written to close, and OpenSSL shipped the repair in 1.0.2h and 1.0.1t [@cve_2016_2107] [@openssl_secadv_20160503]. It did not travel alone. Amazon&apos;s s2n library added a Lucky Thirteen countermeasure that still leaked, revived as Lucky Microseconds by Martin Albrecht and Kenneth Paterson -- and the randomized delays s2n had added as masking were part of the problem [@lucky_microseconds2016].&lt;/p&gt;
&lt;p&gt;Then, in 2019, Craig Young&apos;s Zombie POODLE and GOLDENDOODLE and a large Internet scan by Robert Merget and colleagues found CBC padding oracles still live in 1.83% of the Alexa Top Million, distinguishable by the &lt;em&gt;content&lt;/em&gt; of server responses with no precise timing required at all [@merget2019] [@qualys_zombie_poodle2019].&lt;/p&gt;
&lt;p&gt;Zombie POODLE is an invalid-padding, valid-MAC oracle; GOLDENDOODLE is the mirror image, a valid-padding, invalid-MAC oracle. Different verdicts, same atom.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Incident&lt;/th&gt;
&lt;th&gt;Year&lt;/th&gt;
&lt;th&gt;Leak channel&lt;/th&gt;
&lt;th&gt;Root cause&lt;/th&gt;
&lt;th&gt;Primary source&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;Vaudenay&lt;/td&gt;
&lt;td&gt;2002&lt;/td&gt;
&lt;td&gt;Distinct padding error&lt;/td&gt;
&lt;td&gt;MtE-CBC observable branch&lt;/td&gt;
&lt;td&gt;[@vaudenay2002]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Canvel et al.&lt;/td&gt;
&lt;td&gt;2003&lt;/td&gt;
&lt;td&gt;Coarse timing&lt;/td&gt;
&lt;td&gt;MAC computed vs skipped&lt;/td&gt;
&lt;td&gt;[@canvel2003]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ASP.NET (Rizzo, Duong)&lt;/td&gt;
&lt;td&gt;2010&lt;/td&gt;
&lt;td&gt;Distinct HTTP error&lt;/td&gt;
&lt;td&gt;Padding vs data error revealed&lt;/td&gt;
&lt;td&gt;[@rizzo_duong_woot2010] [@cve_2010_3332]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Lucky Thirteen&lt;/td&gt;
&lt;td&gt;2013&lt;/td&gt;
&lt;td&gt;Statistical timing&lt;/td&gt;
&lt;td&gt;Strip-then-MAC length dependence&lt;/td&gt;
&lt;td&gt;[@lucky13_2013] [@cve_2013_0169]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;POODLE&lt;/td&gt;
&lt;td&gt;2014&lt;/td&gt;
&lt;td&gt;MAC pass or fail on shifted byte&lt;/td&gt;
&lt;td&gt;SSL 3.0 unchecked padding&lt;/td&gt;
&lt;td&gt;[@poodle_writeup] [@cve_2014_3566]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TLS-POODLE&lt;/td&gt;
&lt;td&gt;2014&lt;/td&gt;
&lt;td&gt;Same, no downgrade&lt;/td&gt;
&lt;td&gt;TLS stacks copying the lax check&lt;/td&gt;
&lt;td&gt;[@cve_2014_8730]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CVE-2016-2107&lt;/td&gt;
&lt;td&gt;2016&lt;/td&gt;
&lt;td&gt;Distinguishable alert (bad_record_mac vs record_overflow)&lt;/td&gt;
&lt;td&gt;Constant-time fix slipped a length check&lt;/td&gt;
&lt;td&gt;[@cve_2016_2107]&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Zombie POODLE, GOLDENDOODLE&lt;/td&gt;
&lt;td&gt;2019&lt;/td&gt;
&lt;td&gt;Content differences in responses&lt;/td&gt;
&lt;td&gt;Deployed CBC-HMAC long tail, 1.83%&lt;/td&gt;
&lt;td&gt;[@merget2019] [@qualys_zombie_poodle2019]&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Never return a distinct &quot;bad padding&quot; error, and never let padding validity change a branch or the timing of the response. Any distinguishable decryption outcome on attacker-chosen ciphertext -- an error string, a millisecond, a TCP reset, a difference in response content -- is a padding oracle. The channel does not matter; the observability does.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;One clarification sharpens the definition by contrast. BEAST (2011) is often filed next to these breaks, but it is &lt;em&gt;not&lt;/em&gt; a padding oracle: it is a &lt;a href=&quot;https://paragmali.com/blog/predictable-or-repeated-the-only-two-ways-cryptographic-rand/&quot; rel=&quot;noopener&quot;&gt;predictable-initialization-vector&lt;/a&gt;, chosen-plaintext attack on CBC [@beast_2011], and it belongs to the IV story of Part 2 and Part 5, not here.The tell is the direction of information flow. A padding oracle recovers &lt;em&gt;unknown&lt;/em&gt; plaintext from the receiver&apos;s verdict on the attacker&apos;s ciphertext. BEAST confirms &lt;em&gt;guessed&lt;/em&gt; plaintext by exploiting a predictable IV. Different precondition, different fix -- naming it here only sharpens what a padding oracle is.&lt;/p&gt;
&lt;p&gt;Five generations, five channels, one bit. Every fix silenced the loudest remaining leak, and every fix was beaten by the next-quietest -- including a fix that became its own oracle. Whack-a-mole is not a strategy; it is a symptom. And the symptom points at a diagnosis the field had actually proved correct two years before Vaudenay&apos;s attack, and simply had not deployed.&lt;/p&gt;
&lt;h2&gt;5. The Bug Is the Order, Not the Padding&lt;/h2&gt;
&lt;p&gt;Stop looking at the padding. A padding oracle is not a padding bug; it is the consequence of &lt;em&gt;checking padding on data you have not authenticated&lt;/em&gt;. Change what you check first, and the oracle has nothing to answer. To see why, you need the piece the attack has been quietly relying on: the MAC, and the three ways to combine it with encryption.&lt;/p&gt;

A MAC is a keyed tag that proves a message is authentic and unmodified: only a holder of the secret key can produce a tag that verifies. HMAC is the deployed instance. A valid MAC on a ciphertext means the ciphertext is genuine -- it came from someone with the key, and nothing tampered with it. Part 3 covers HMAC&apos;s structure; here the point is simply *when* you check it.
&lt;p&gt;In 2000, Mihir Bellare and Chanathip Namprempre formalized the three &quot;generic composition&quot; ways to bolt a MAC onto an encryption scheme and proved exactly what each one buys [@bellare_namprempre2000]. Each maps cleanly onto a real protocol.&lt;/p&gt;

Compute the MAC over the *plaintext*, then encrypt the plaintext, MAC, and padding together. On receipt the order is forced: decrypt, strip padding, *then* verify the MAC. Because the receiver must decrypt and unpad before it can check authenticity, the padding verdict is observable by construction. This is SSL and TLS&apos;s original choice -- and the reason SSL/TLS, and only SSL/TLS, became a fifteen-year catalog of padding oracles.

Encrypt the plaintext, then compute the MAC over the *ciphertext* (and the IV), and transmit ciphertext plus tag. On receipt, verify the tag *first*; decrypt only if it passes. A ciphertext the attacker altered fails the MAC and is discarded before any padding logic runs -- so there is no padding verdict to leak, through any channel. This is IPsec ESP&apos;s design and the shape RFC 7366 retrofits onto TLS.
&lt;p&gt;The third sibling, encrypt-and-MAC, tags the plaintext and sends the tag alongside the ciphertext; SSH chose it. It still forces decryption before verification, and because the tag is over the plaintext it can leak plaintext equality, so it is no cure [@bellare_kohno_namprempre_ssh2002].&lt;/p&gt;

flowchart TD
    subgraph MtE[&quot;MAC-then-encrypt (SSL and TLS)&quot;]
        M1[&quot;Decrypt&quot;] --&amp;gt; M2[&quot;Strip padding&quot;] --&amp;gt; M3[&quot;Verify MAC&quot;]
    end
    subgraph EnM[&quot;Encrypt-and-MAC (SSH)&quot;]
        E1[&quot;Decrypt&quot;] --&amp;gt; E2[&quot;Verify MAC over plaintext&quot;]
    end
    subgraph EtM[&quot;Encrypt-then-MAC (IPsec ESP)&quot;]
        T1[&quot;Verify MAC over IV and ciphertext&quot;] --&amp;gt; T2[&quot;Decrypt&quot;] --&amp;gt; T3[&quot;Strip padding&quot;]
    end
&lt;p&gt;Look at where the MAC check sits. In the first two, unauthenticated ciphertext reaches the decrypt-and-parse machinery &lt;em&gt;before&lt;/em&gt; anyone has proven it genuine. In the third, a forged ciphertext dies at the door.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Composition&lt;/th&gt;
&lt;th&gt;MAC covers&lt;/th&gt;
&lt;th&gt;Receiver order&lt;/th&gt;
&lt;th&gt;IND-CCA (generic)&lt;/th&gt;
&lt;th&gt;INT-CTXT (generic)&lt;/th&gt;
&lt;th&gt;Protocol&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;MAC-then-encrypt&lt;/td&gt;
&lt;td&gt;Plaintext&lt;/td&gt;
&lt;td&gt;Decrypt, unpad, verify&lt;/td&gt;
&lt;td&gt;Not guaranteed&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;SSL and TLS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Encrypt-and-MAC&lt;/td&gt;
&lt;td&gt;Plaintext&lt;/td&gt;
&lt;td&gt;Decrypt, verify&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;SSH&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Encrypt-then-MAC&lt;/td&gt;
&lt;td&gt;Ciphertext and IV&lt;/td&gt;
&lt;td&gt;Verify, decrypt, unpad&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;IPsec ESP&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Now the theorem, stated precisely. Encrypt-then-MAC, composed from an IND-CPA encryption scheme and a strongly unforgeable MAC (SUF-CMA), generically attains both IND-CCA and integrity of ciphertexts -- the strongest guarantees of the three -- while MAC-then-encrypt and encrypt-and-MAC do not generically reach integrity of ciphertexts [@bellare_namprempre2000]. Strong unforgeability is the load-bearing word: with only ordinary unforgeability, a second valid tag on the same ciphertext is a fresh accepted ciphertext, and the guarantee weakens to integrity of &lt;em&gt;plaintexts&lt;/em&gt;. In practice the qualifier is free, because HMAC is strongly unforgeable, so the deployed guarantee is exactly as stated. That is the whole game: a construction with integrity of ciphertexts rejects any forged ciphertext &lt;em&gt;before&lt;/em&gt; decryption, so a padding oracle cannot form.&lt;/p&gt;
&lt;p&gt;One honest caveat, because the sloppy version of this claim is wrong. Hugo Krawczyk proved in 2001 that MAC-then-encrypt &lt;em&gt;is&lt;/em&gt; secure when the encryption is CBC or counter mode, under stated conditions [@krawczyk2001]. So MtE is not &quot;always broken.&quot;Krawczyk&apos;s result is conditional: MtE with CBC or CTR is provably secure &lt;em&gt;in the model&lt;/em&gt;, which means Canvel, Lucky Thirteen, and POODLE are implementation, side-channel, and underspecified-padding failures -- not refutations of a theorem. The precise statement guards against the overreach &quot;MtE is broken.&quot; What is true is subtler and more useful: MtE is conditionally provable and practically fragile, because it leaves unauthenticated ciphertext reaching the parser, and holding the model&apos;s assumptions in compiled code is the hard part [@krawczyk2001]. The practitioner&apos;s takeaway is not &quot;the theorem was wrong&quot; but &quot;the theorem&apos;s assumptions are unreasonably hard to preserve in real code -- so change the order and stop needing them.&quot; The practitioner literature has said as much for years [@ferguson_schneier_kohno_ce].&lt;/p&gt;
&lt;p&gt;Watch the difference on a single tampered ciphertext. The MtE receiver leaks a distinguishable outcome; the EtM receiver returns one indistinguishable failure before it parses anything.&lt;/p&gt;
&lt;p&gt;{`
// Two toy receivers over the SAME tampered ciphertext.
// No real crypto: &quot;decrypt&quot; and &quot;macOk&quot; are stand-ins so you can see
// what each receiver reveals.
function decrypt(ct)  { return { paddingValid: false }; } // tampering broke the pad
function macOk(ct)    { return ct.tag === &quot;authentic&quot;; }  // forged tag fails&lt;/p&gt;
&lt;p&gt;const tampered = { tag: &quot;forged&quot; };&lt;/p&gt;
&lt;p&gt;// MAC-then-encrypt: decrypt and check padding BEFORE the MAC.
function mteReceive(ct) {
  const p = decrypt(ct);
  if (!p.paddingValid) return &quot;PADDING_ERROR&quot;;  // leaks the padding verdict
  if (!macOk(ct))      return &quot;MAC_ERROR&quot;;
  return &quot;OK&quot;;
}&lt;/p&gt;
&lt;p&gt;// Encrypt-then-MAC: verify the MAC FIRST; never touch padding on forgeries.
function etmReceive(ct) {
  if (!macOk(ct)) return &quot;FAIL&quot;;                // one indistinguishable outcome
  const p = decrypt(ct);
  if (!p.paddingValid) return &quot;FAIL&quot;;
  return &quot;OK&quot;;
}&lt;/p&gt;
&lt;p&gt;console.log(&quot;MtE says:&quot;, mteReceive(tampered)); // PADDING_ERROR (distinguishable)
console.log(&quot;EtM says:&quot;, etmReceive(tampered)); // FAIL (nothing to learn)
`}&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key idea:&lt;/strong&gt; The bug is the &lt;em&gt;order&lt;/em&gt;, not the padding. Silencing channels is whack-a-mole; checking authenticity &lt;em&gt;first&lt;/em&gt; -- Encrypt-then-MAC, and its successor AEAD -- removes the precondition that unauthenticated ciphertext ever reaches a padding check, so the oracle has nothing left to answer.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That is Aha number two, and it reorganizes the entire catalog. Every generation before Encrypt-then-MAC silenced a channel while leaving the precondition intact; the last two remove the precondition itself.&lt;/p&gt;

flowchart TD
    G1[&quot;Gen 1, distinct padding error&quot;] --&amp;gt; G2[&quot;Gen 2, unified alert&quot;]
    G2 --&amp;gt; G3[&quot;Gen 3, constant-time MtE&quot;]
    G3 --&amp;gt; G4[&quot;Gen 4, Encrypt-then-MAC&quot;]
    G4 --&amp;gt; G5[&quot;Gen 5, AEAD&quot;]
    G1 -.-&amp;gt; S[&quot;Silenced a channel, precondition intact&quot;]
    G2 -.-&amp;gt; S
    G3 -.-&amp;gt; S
    G4 -.-&amp;gt; R[&quot;Removed the precondition, authenticity first&quot;]
    G5 -.-&amp;gt; R

The three compositions were not tried in sequence; they were chosen in *parallel* by different protocols and coexisted for years. MAC-then-encrypt went into SSL and TLS, encrypt-and-MAC into SSH, Encrypt-then-MAC into IPsec ESP [@bellare_namprempre2000]. Only the MtE branch became the fifteen-year catalog. IPsec&apos;s Encrypt-then-MAC branch was structurally immune from the start, which is the quiet punchline of the whole story: the fix TLS reached for in 2014 was, in effect, &quot;become IPsec&quot; -- the negotiated `encrypt_then_mac` extension of RFC 7366 [@rfc7366].
&lt;p&gt;AEAD (RFC 5116) is Encrypt-then-MAC&apos;s standardized successor: one primitive, authenticity intrinsic, no composition knob to misset [@rfc5116]. The theorem was published in 2000. Vaudenay&apos;s attack landed in 2002. And the fix did not reach the deployed TLS base until 2014 to 2018. The rest of this article is what &quot;check authenticity first&quot; looks like when it finally shipped at Internet scale -- and why, even now, the class is not entirely dead.&lt;/p&gt;
&lt;h2&gt;6. The State of the Art: Closed by Construction&lt;/h2&gt;
&lt;p&gt;The modern answer is boring, and that is exactly the point. Use a primitive where a padding oracle &lt;em&gt;cannot exist&lt;/em&gt;, because authenticity is checked first and there is no block padding at all.&lt;/p&gt;

An AEAD primitive takes a key, a nonce, a plaintext, and optional associated data, and produces a ciphertext with an integral authentication tag. Decryption verifies the tag as part of the operation and returns *nothing* -- a single indistinguishable failure -- if it does not match. There is no separate MAC to order, and for the stream-based AEADs in wide use there is no block padding to be oracular. Standardized as an interface in RFC 5116 [@rfc5116].
&lt;p&gt;Two AEADs carry nearly all modern traffic. &lt;strong&gt;AES-GCM&lt;/strong&gt; is the default where AES hardware exists: it is AES in counter mode for encryption, plus a GHASH tag over the ciphertext and associated data, verified before any plaintext is released. &lt;strong&gt;ChaCha20-Poly1305&lt;/strong&gt; is the default where AES hardware does not exist -- Google deployed it in Chrome on Android because it runs about three times faster than AES-GCM on devices without AES acceleration, and it is constant-time in portable software by design [@rfc8439] [@google_chacha_blog2014]. Both are counter-mode stream constructions, and that structural fact, not the label &quot;AEAD,&quot; is why they are immune.A stream construction has &lt;em&gt;no block padding at all&lt;/em&gt; -- the keystream is simply truncated to the plaintext length. So the object Vaudenay&apos;s atom acts on, a padded final block whose validity the receiver checks, does not exist. There is no surface. This is why &quot;GCM has a tag, could it have a padding oracle?&quot; answers itself: there is nothing to pad.&lt;/p&gt;
&lt;p&gt;Then the standards removed the choice. TLS 1.3 (RFC 8446, 2018) removes CBC cipher suites entirely and mandates AEAD, and QUIC inherits that record layer, so a conformant modern stack cannot express the padding-oracle precondition [@rfc8446].&lt;/p&gt;
&lt;p&gt;For the deployed base that could not jump straight to 1.3, RFC 7366 (2014) retrofits Encrypt-then-MAC onto TLS 1.0 through 1.2 as a negotiated extension -- extension type 22 -- citing Bellare-Namprempre and Krawczyk by name as the reason MtE &quot;is no longer regarded as secure&quot; [@rfc7366]. And BCP 195 (RFC 9325, 2022) codifies the operational rule: prefer TLS 1.3, prefer AEAD suites on 1.2, and recommend against CBC [@rfc9325].&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Method&lt;/th&gt;
&lt;th&gt;How it works&lt;/th&gt;
&lt;th&gt;What it buys&lt;/th&gt;
&lt;th&gt;Status 2024 to 2026&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;AES-GCM&lt;/td&gt;
&lt;td&gt;AES-CTR plus GHASH tag, verified first&lt;/td&gt;
&lt;td&gt;No padding surface, fastest with AES hardware&lt;/td&gt;
&lt;td&gt;Default (TLS 1.3, QUIC)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ChaCha20-Poly1305&lt;/td&gt;
&lt;td&gt;ChaCha20 plus Poly1305, verified first&lt;/td&gt;
&lt;td&gt;Constant-time in software, ~3x without AES-NI&lt;/td&gt;
&lt;td&gt;Co-default, WireGuard, OpenSSH&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Encrypt-then-MAC (RFC 7366)&lt;/td&gt;
&lt;td&gt;MAC over IV and ciphertext, verified first&lt;/td&gt;
&lt;td&gt;Structural fix for legacy CBC&lt;/td&gt;
&lt;td&gt;Compose-by-hand rule&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Constant-time CBC-HMAC&lt;/td&gt;
&lt;td&gt;Harden the MtE order in place&lt;/td&gt;
&lt;td&gt;Nothing new; fragile&lt;/td&gt;
&lt;td&gt;Deprecated (BCP 195)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Misuse-resistant, committing AEAD&lt;/td&gt;
&lt;td&gt;AES-GCM-SIV, committing wrappers&lt;/td&gt;
&lt;td&gt;Nonce-repeat safety, key commitment&lt;/td&gt;
&lt;td&gt;Ascending, niche&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Padding-oracle detection&lt;/td&gt;
&lt;td&gt;TLS-Attacker, padcheck scanners&lt;/td&gt;
&lt;td&gt;Measures residual exposure&lt;/td&gt;
&lt;td&gt;Active tooling&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;The honest status has two levels. At the primitive level the class is &lt;em&gt;closed&lt;/em&gt;: TLS 1.3 has no CBC-HMAC record layer, so it has no padding oracle. But CBC-HMAC survives in the long tail -- legacy TLS, VPN appliances, load balancers, IoT stacks, and home-grown application crypto -- which is why padding oracles are still discovered, in 1.83% of the top million as recently as the 2019 scan, exploitable without precise timing [@merget2019].&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Treat any distinguishable decryption error on a CBC-HMAC endpoint as a reportable finding, not a performance bug to smooth over. The lesson of five generations is that the construction, not any single leak, is the problem: CBC-HMAC is the thing to &lt;em&gt;retire&lt;/em&gt;. Every hour spent hardening its constant-time behavior is an hour not spent migrating to AEAD, which needs no such hardening.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&quot;Use AEAD&quot; is the entire answer for greenfield code. But engineers inherit constraints -- a mode they cannot change, a library without AEAD, a protocol mid-migration -- so the real question is not &quot;what is best&quot; but &quot;what are my options, ranked, and exactly when does each apply?&quot;&lt;/p&gt;
&lt;h2&gt;7. How to Not Have a Padding Oracle, Ranked&lt;/h2&gt;
&lt;p&gt;There are three ways to keep a padding oracle out of your system, in strict order of durability -- and the ranking is a theorem, not a taste. The yardstick is one question: &lt;em&gt;is authenticity checked before anything parses the plaintext?&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;First choice: AEAD.&lt;/strong&gt; Authenticity is intrinsic, there is no padding, and there are &lt;em&gt;zero composition knobs&lt;/em&gt; for an implementer to misset. This is the default, full stop. Its own frontier is a genuinely different problem, not a padding successor: nonce reuse under AES-GCM is catastrophic -- the &quot;forbidden attack,&quot; demonstrated against real TLS and IPMI stacks by Böck et al. (WOOT &apos;16) [@bock_nonce_woot2016] -- and standard AEAD is not key-committing, so one ciphertext can be made to open to different valid plaintexts under different keys [@albertini_committing2022]. Those are addressed by AES-GCM-SIV for nonce-misuse resistance and by committing AEAD constructions, which the CFRG&apos;s property vocabulary now names formally [@rfc8452] [@rfc9771]. Treat that as a one-line signpost, not a reason to hesitate on AEAD.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Second choice: Encrypt-then-MAC by hand.&lt;/strong&gt; Correct, but only if you get three things right -- an &lt;em&gt;independent&lt;/em&gt; MAC key (never the encryption key), coverage of &lt;em&gt;both&lt;/em&gt; the IV and the ciphertext, and a &lt;em&gt;constant-time&lt;/em&gt; tag comparison performed &lt;em&gt;before&lt;/em&gt; any decrypt or unpad. That is the RFC 7366 shape [@rfc7366]. Three knobs an implementer can misset is precisely the argument for reaching for AEAD&apos;s zero knobs whenever you can.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Third choice, and only if forced: constant-time MtE / CBC-HMAC.&lt;/strong&gt; This is the &quot;make the existing order safe in place&quot; path, and it is the cautionary option to &lt;em&gt;retire&lt;/em&gt;, not to choose. Lucky Thirteen, Lucky Microseconds, and CVE-2016-2107 are the evidence that it fights the construction: MtE keeps unauthenticated ciphertext reaching the parser, so the distinguisher re-emerges through whatever the CPU or the compiler leaves open [@lucky13_2013] [@cve_2016_2107].&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Property&lt;/th&gt;
&lt;th&gt;AES-GCM&lt;/th&gt;
&lt;th&gt;ChaCha20-Poly1305&lt;/th&gt;
&lt;th&gt;Encrypt-then-MAC (CBC-HMAC)&lt;/th&gt;
&lt;th&gt;Constant-time MtE (CBC-HMAC)&lt;/th&gt;
&lt;th&gt;AES-GCM-SIV&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;Authenticity checked first?&lt;/td&gt;
&lt;td&gt;Yes (intrinsic)&lt;/td&gt;
&lt;td&gt;Yes (intrinsic)&lt;/td&gt;
&lt;td&gt;Yes (verify tag first)&lt;/td&gt;
&lt;td&gt;No (decrypt and unpad first)&lt;/td&gt;
&lt;td&gt;Yes (intrinsic)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Padding-oracle surface&lt;/td&gt;
&lt;td&gt;None (stream)&lt;/td&gt;
&lt;td&gt;None (stream)&lt;/td&gt;
&lt;td&gt;None (rejected pre-unpad)&lt;/td&gt;
&lt;td&gt;Full (precondition survives)&lt;/td&gt;
&lt;td&gt;None (stream)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Composition knobs to misset&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Many&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Nonce or IV reuse behavior&lt;/td&gt;
&lt;td&gt;Catastrophic&lt;/td&gt;
&lt;td&gt;Catastrophic&lt;/td&gt;
&lt;td&gt;IV must be unpredictable&lt;/td&gt;
&lt;td&gt;IV must be unpredictable&lt;/td&gt;
&lt;td&gt;Graceful (equality leak only)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Status 2024 to 2026&lt;/td&gt;
&lt;td&gt;Active default&lt;/td&gt;
&lt;td&gt;Active co-default&lt;/td&gt;
&lt;td&gt;Compose-by-hand rule&lt;/td&gt;
&lt;td&gt;Deprecated (BCP 195)&lt;/td&gt;
&lt;td&gt;Ascending, niche&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;p&gt;Only the first two options remove the &lt;em&gt;precondition&lt;/em&gt;; the third merely silences the loudest channel. That is the whole ranking, and it maps onto the parallel history from the last section: AEAD and Encrypt-then-MAC are what IPsec effectively had from the start, while constant-time MtE is the fifteen-year attempt to make SSL/TLS&apos;s original order safe without changing it.&lt;/p&gt;
&lt;p&gt;So the durable options all share one property: they refuse to let unauthenticated ciphertext reach a parser. That raises a deeper question a careful reader should now be asking. &lt;em&gt;Why&lt;/em&gt; is one accept/reject bit enough to be fatal in the first place -- and is Encrypt-then-MAC &lt;em&gt;provably&lt;/em&gt; enough to stop it, or just empirically better so far?&lt;/p&gt;
&lt;h2&gt;8. Why a Bit Is Enough, and Why the Fix Is a Theorem&lt;/h2&gt;
&lt;p&gt;Two boundaries frame the whole subject: how &lt;em&gt;little&lt;/em&gt; leakage the attacker needs, which is frighteningly little, and how &lt;em&gt;hard&lt;/em&gt; the defender&apos;s guarantee actually is, which is a proof rather than a hope.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The lower bound: one reliable bit suffices.&lt;/strong&gt; A single accept/reject bit per query, combined with CBC&apos;s malleability, recovers plaintext at about 128 guesses per byte, and the cipher is never attacked [@vaudenay2002]. The consequence is a hard limit on defense that every engineer should internalize: there is no &quot;leak too small to matter,&quot; only &quot;too noisy to measure cheaply.&quot; The bit is information-theoretically sufficient; the only variable is how many queries it takes to read it reliably.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Lucky Thirteen turned a difference of a few tens of CPU cycles -- invisible to a human, buried in network jitter -- into full plaintext recovery by amplifying it statistically over many sessions [@lucky13_2013]. If a one-bit distinguisher exists at all, &quot;make it quieter&quot; only raises the query count; it never reaches zero. The only defense that reduces the leak to &lt;em&gt;nothing&lt;/em&gt; is removing the precondition, so no bit is ever produced.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;strong&gt;The impossibility in the middle.&lt;/strong&gt; An unauthenticated, malleable mode is provably not IND-CCA2. CBC without integrity is a decryption oracle by construction, so no amount of error-message hygiene or timing equalization can rescue MAC-then-encrypt CBC against a determined side-channel adversary. The impossibility lives in the &lt;em&gt;precondition itself&lt;/em&gt; -- in letting a malleable ciphertext be decrypted before it is authenticated -- which is why the deployed padding oracle is exactly the &lt;a href=&quot;https://paragmali.com/blog/secure-against-whom-the-security-definitions-every-protocol-/&quot; rel=&quot;noopener&quot;&gt;IND-CCA2 break&lt;/a&gt; this series defines in Part 1.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The upper bound: the fix is a hard theorem.&lt;/strong&gt; The best achievable defense is a proved one.&lt;/p&gt;

A scheme has integrity of ciphertexts when an adversary, even after seeing many valid ciphertexts, cannot produce *any* new ciphertext the receiver accepts as valid. This is the property that closes the oracle: if every forged ciphertext is rejected before decryption, the receiver never reveals a padding verdict on attacker-chosen input. Encrypt-then-MAC and AEAD provide it; MAC-then-encrypt does not, generically.
&lt;p&gt;Encrypt-then-MAC with an IND-CPA cipher and a strongly unforgeable MAC (SUF-CMA) generically attains IND-CCA plus integrity of ciphertexts, so a forged ciphertext is rejected with overwhelming probability &lt;em&gt;before&lt;/em&gt; decryption -- the oracle is closed by theorem, not by patch [@bellare_namprempre2000]. Krawczyk marks the honest boundary of the alternative: MAC-then-encrypt is conditionally provable for CBC and counter mode, so the real-world MtE breaks are implementation and side-channel failures, not refutations of a theorem -- but Encrypt-then-MAC needs no such conditions [@krawczyk2001].&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Key idea:&lt;/strong&gt; At the level of construction design there is no gap. The lower bound -- any observable padding verdict yields byte-at-a-time decryption -- is &lt;em&gt;met&lt;/em&gt; by removing the precondition, and the upper bound -- reject forgeries before decryption, IND-CCA plus INT-CTXT -- is &lt;em&gt;achieved&lt;/em&gt; by Encrypt-then-MAC and AEAD. Best-possible and best-achieved coincide. Every remaining gap is implementation and deployment, not mathematics.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;That is Aha number three, and it is a humbler note than it first sounds. If the design problem is provably solved, why do scanners still find these oracles in production, and why did a correct construction get re-broken by its own patch? Because &quot;solved on paper&quot; and &quot;solved in a compiled binary on a specific CPU, deployed to a billion devices&quot; are different sentences -- and the space between them is where the class still lives.&lt;/p&gt;
&lt;h2&gt;9. Where Padding Oracles Still Live&lt;/h2&gt;
&lt;p&gt;The construction-level problem is closed; the &lt;em&gt;class&lt;/em&gt; is not. Here are four places the 2002 bug remains exploitable, and one frontier that would end the whack-a-mole.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Constant-time is a moving target.&lt;/strong&gt; Even with the correct construction, a specific compiled binary on a specific CPU can reintroduce a secret-dependent timing difference through branch prediction, cache behavior, or an assembly fast path -- and the countermeasure can also slip in a plainer way. CVE-2016-2107 is the canonical evidence: OpenSSL&apos;s AES-NI fast path, written to make the fix constant-time, dropped a length check and re-created the oracle -- leaking not through timing but through a distinguishable alert (bad_record_mac versus record_overflow), which is why TLS-Attacker found it by clustering responses rather than measuring cycles [@cve_2016_2107]. The strongest partial answer is formally verified, secret-independent cryptographic code -- HACL* and EverCrypt prove memory safety, functional correctness, &lt;em&gt;and&lt;/em&gt; secret independence, and ship in Firefox and NSS [@hacl_star]. But that covers primitives, not yet whole record layers end to end, so the verification frontier is real.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The deployed CBC-HMAC long tail.&lt;/strong&gt; Legacy TLS, VPN appliances, load balancers, and IoT stacks that cannot move to TLS 1.3 keep MAC-then-encrypt CBC alive. The 2019 scan put the exposure at 1.83% of the top million, exploitable without precise timing, and the population shrinks year over year rather than vanishing -- a trend the SSL Pulse dashboard tracks [@merget2019] [@ssl_pulse].&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Application-layer oracles.&lt;/strong&gt; This is the part TLS 1.3 does not reach, and it deserves its own aside because it is where the next decade of these bugs will be found.&lt;/p&gt;

Tibor Jager and Juraj Somorovsky broke the W3C XML Encryption standard with the same oracle, decrypting XML in SOAP and web-services stacks by watching how an endpoint reacted to manipulated ciphertext -- proof that the mechanism operates far outside TLS [@jager_somorovsky_xmlenc2011]. The application layer is full of the same shape: JWE and JOSE `A128CBC-HS256` tokens, encrypted cookies, and ViewState-style tokens reproduce the 2002 bug, often with no MAC at all, above TLS where network scanners cannot see them. A separate but adjacent hazard, Efail, exploited a CBC *malleability gadget* in S/MIME and a CFB gadget in OpenPGP to exfiltrate plaintext directly -- not a padding-verdict oracle, but the same &quot;unauthenticated malleable ciphertext&quot; precondition wearing different clothes [@efail2018].
&lt;p&gt;&lt;strong&gt;Format and parse oracles beyond padding.&lt;/strong&gt; Generalize once more: &lt;em&gt;any&lt;/em&gt; observable post-decryption structural verdict -- a JSON parse success, a protobuf validity check, a decompression outcome -- is a padding oracle by another name whenever it acts on unauthenticated ciphertext. The padding was only ever the first structure anyone checked. Jager and Somorovsky already proved the mechanism outside padding entirely [@jager_somorovsky_xmlenc2011], and systematic detection of &quot;decrypt-then-parse&quot; leaks is not yet automated the way TLS padding-oracle scanning now is.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;AEAD&apos;s own frontier.&lt;/strong&gt; The successor primitive has two sharp edges of its own -- catastrophic nonce reuse in AES-GCM [@bock_nonce_woot2016] and the lack of key commitment [@albertini_committing2022] -- which matter most in exactly the places large systems trip: nonce management across many distributed senders, and multi-key or multi-recipient contexts like JWT and envelope encryption. Both are addressed &lt;em&gt;separately&lt;/em&gt; today, by AES-GCM-SIV for misuse resistance and by committing constructions that RFC 9771&apos;s vocabulary now specifies, but no single ubiquitous primitive yet delivers misuse-resistant &lt;em&gt;and&lt;/em&gt; committing &lt;em&gt;and&lt;/em&gt; fast &lt;em&gt;and&lt;/em&gt; online at once [@rfc8452] [@rfc9771]. That convergence is the open engineering target, and it is a different problem from the padding oracle, not a continuation of it.&lt;/p&gt;
&lt;p&gt;Every one of these is the same sentence wearing new clothes: unauthenticated, attacker-chosen input reaching a check whose verdict is observable. Which means the practical guide almost writes itself -- it is the thesis made operational.&lt;/p&gt;
&lt;h2&gt;10. Decision Rules: Use X With These Params in Case Y&lt;/h2&gt;
&lt;p&gt;Everything above collapses into a short decision procedure and a shorter list of nevers.&lt;/p&gt;

flowchart TD
    Start[&quot;Need authenticated encryption&quot;] --&amp;gt; Q1{&quot;Greenfield design?&quot;}
    Q1 --&amp;gt;|&quot;Yes&quot;| Q2{&quot;AES hardware present?&quot;}
    Q2 --&amp;gt;|&quot;Yes&quot;| GCM[&quot;AES-GCM, unique 96-bit nonce&quot;]
    Q2 --&amp;gt;|&quot;No&quot;| ChaCha[&quot;ChaCha20-Poly1305&quot;]
    Q1 --&amp;gt;|&quot;No, stuck with CBC&quot;| Q3{&quot;Can you retire CBC?&quot;}
    Q3 --&amp;gt;|&quot;Yes&quot;| GCM
    Q3 --&amp;gt;|&quot;No&quot;| EtM[&quot;Encrypt-then-MAC, RFC 7366, verify first&quot;]
    GCM --&amp;gt; Q4{&quot;Nonce uniqueness hard?&quot;}
    Q4 --&amp;gt;|&quot;Yes&quot;| SIV[&quot;AES-GCM-SIV&quot;]
    Q4 --&amp;gt;|&quot;No&quot;| Done[&quot;Ship it&quot;]
&lt;p&gt;The rules, in order:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Default to a vetted AEAD, from a library you cannot misuse.&lt;/strong&gt; Use AES-GCM with a unique 96-bit nonce and fewer than $2^{32}$ messages per key where the CPU has AES-NI; use ChaCha20-Poly1305 where there is no AES hardware, or when you want constant-time behavior from portable software [@rfc8439]. In application code, reach for libsodium&apos;s &lt;code&gt;secretbox&lt;/code&gt; or Google Tink rather than raw primitives, and prefer any API where you &lt;em&gt;cannot&lt;/em&gt; express &quot;decrypt, then check&quot; -- the safest libraries never hand you plaintext before verification.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;If nonce uniqueness is hard to guarantee&lt;/strong&gt; across many independent senders or under snapshot and rollback risk, use AES-GCM-SIV, so a nonce repeat degrades gracefully instead of catastrophically [@rfc8452].&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;If a ciphertext may be opened under more than one key&lt;/strong&gt; -- JWT and JOSE, envelope encryption, abuse reporting -- use a committing AEAD [@albertini_committing2022] [@rfc9771].&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;If you must compose by hand,&lt;/strong&gt; use Encrypt-then-MAC: encrypt, then compute the MAC over the IV and ciphertext with an &lt;em&gt;independent&lt;/em&gt; MAC key, and verify the tag in constant time &lt;em&gt;before&lt;/em&gt; you decrypt or unpad [@rfc7366].&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;On a legacy TLS 1.2 CBC deployment you cannot yet retire,&lt;/strong&gt; negotiate RFC 7366 Encrypt-then-MAC, prefer AEAD suites, and follow BCP 195 [@rfc9325].&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Never&lt;/strong&gt; return a distinct &quot;bad padding&quot; error; never branch or vary timing on padding validity; never decrypt before verifying; never reuse the encryption key as the MAC key; never leave the IV out of the MAC; and never ship SSL 3.0 or non-RFC-7366 CBC.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you are stuck at the fourth rule, the shape below is the reference. It is dependency-free and non-cryptographic on purpose: copy the &lt;em&gt;order of operations&lt;/em&gt;, not the stand-in primitives.&lt;/p&gt;
&lt;p&gt;{`
// Reference SHAPE for a correct Encrypt-then-MAC open().
// constantTimeEqual, hmac, cbcDecrypt, removePkcs7 are stand-ins, NOT real crypto.
function constantTimeEqual(a, b) {
  if (a.length !== b.length) return false;
  let diff = 0;
  for (let i = 0; i &amp;lt; a.length; i++) diff |= a[i] ^ b[i];
  return diff === 0;   // no early exit: time is independent of where they differ
}&lt;/p&gt;
&lt;p&gt;function open(kEnc, kMac, iv, ciphertext, tag) {
  // 1. Authenticate FIRST, over IV concat ciphertext, in constant time.
  const expected = hmac(kMac, iv.concat(ciphertext));
  if (!constantTimeEqual(tag, expected)) return &quot;FAIL&quot;;  // one outcome, always&lt;/p&gt;
&lt;p&gt;  // 2. Only authentic ciphertext ever reaches decryption and unpadding.
  const padded = cbcDecrypt(kEnc, iv, ciphertext);
  const plain = removePkcs7(padded);
  return plain === null ? &quot;FAIL&quot; : plain;                // same FAIL either way
}&lt;/p&gt;
&lt;p&gt;// Stand-ins so the snippet runs (NOT secure):
function hmac(k, data)          { return data.map((b) =&amp;gt; (b ^ k) &amp;amp; 0xff); }
function cbcDecrypt(k, iv, c)   { return c; }
function removePkcs7(p)         { return p; }&lt;/p&gt;
&lt;p&gt;console.log(open(1, 2, [9, 9], [4, 5, 6], [7, 7, 7])); // FAIL: forged tag rejected first
`}&lt;/p&gt;
&lt;p&gt;Every misuse in real code maps one-to-one onto a named break in the catalog. That mapping is the practical guide as a grid.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Misuse seen in code&lt;/th&gt;
&lt;th&gt;The named break it reproduces&lt;/th&gt;
&lt;th&gt;The fix&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td&gt;Distinct &quot;bad padding&quot; error&lt;/td&gt;
&lt;td&gt;Vaudenay, ASP.NET&lt;/td&gt;
&lt;td&gt;Verify authenticity first&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Non-constant-time unpad and MAC&lt;/td&gt;
&lt;td&gt;Lucky Thirteen&lt;/td&gt;
&lt;td&gt;AEAD, or constant-time EtM&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Unchecked pad bytes&lt;/td&gt;
&lt;td&gt;POODLE&lt;/td&gt;
&lt;td&gt;Retire SSL 3.0 and CBC&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AES-NI fast-path length slip&lt;/td&gt;
&lt;td&gt;CVE-2016-2107&lt;/td&gt;
&lt;td&gt;Verified constant-time, or AEAD&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Verifying the MAC after decrypting&lt;/td&gt;
&lt;td&gt;The whole class&lt;/td&gt;
&lt;td&gt;Encrypt-then-MAC ordering&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Nonce reuse under AES-GCM&lt;/td&gt;
&lt;td&gt;The forbidden attack&lt;/td&gt;
&lt;td&gt;Unique nonces, or AES-GCM-SIV&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;App-layer encrypt with no MAC&lt;/td&gt;
&lt;td&gt;JWE and cookie oracles&lt;/td&gt;
&lt;td&gt;A committing AEAD&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Use a vetted AEAD: AES-GCM with unique 96-bit nonces and the under-$2^{32}$-messages-per-key cap where AES hardware exists, or ChaCha20-Poly1305 where it does not. If you must keep CBC, negotiate RFC 7366 Encrypt-then-MAC with an independent MAC key and a constant-time verify performed first. Never ship SSL 3.0 or non-RFC-7366 CBC. That is the entire checklist, because the lesson is a single sentence.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Detection is cheap enough to wire into a pipeline, and you should, because content-difference oracles need no timing rig to find [@merget2019].&lt;/p&gt;

Point an open-source padding-oracle scanner at your own hosts. Craig Young&apos;s `padcheck` and the TLS-Attacker project&apos;s `TLS-Padding-Oracles` both probe for the invalid-padding-versus-invalid-MAC content differences that Zombie POODLE and GOLDENDOODLE exploit, and they run without a precise timing setup [@padcheck] [@tls_padding_oracles]. A clean result on a CBC endpoint is reassuring; any distinguishable response is a reportable finding, not a tuning opportunity.
&lt;p&gt;The checklist is short because the lesson is one sentence. Before restating it, clear the confident, wrong sentences that keep this bug alive in design meetings.&lt;/p&gt;
&lt;h2&gt;11. Misconceptions, Named and Corrected&lt;/h2&gt;
&lt;p&gt;The padding oracle survives mostly because a handful of confident, wrong sentences keep getting said in design meetings. Here they are, corrected.&lt;/p&gt;


The strength of AES is irrelevant to this attack, because the key is never attacked. A padding oracle recovers plaintext from the receiver&apos;s error handling -- a distinguishable answer to &quot;was the padding valid?&quot; -- combined with CBC&apos;s malleability [@vaudenay2002]. The ASP.NET break recovered authentication tickets and `web.config` with AES-256 fully intact [@rizzo_duong_woot2010]. &quot;The cipher is strong&quot; and &quot;the system is secure&quot; are different claims.


No. The same accept/reject bit re-emerges through the next-quietest channel. Once the error message was unified, it leaked through coarse timing (Canvel et al., 2003) and then through statistical timing under unified errors (Lucky Thirteen, 2013), and through unchecked structure entirely (POODLE, 2014) [@canvel2003] [@lucky13_2013] [@poodle_writeup]. Silencing one channel just moves the leak; only removing the precondition closes it.


No -- and the overstatement matters. Krawczyk proved MAC-then-encrypt conditionally secure for CBC and counter mode, so the real-world breaks are implementation, side-channel, and underspecified-padding failures, not refutations of a theorem [@krawczyk2001]. What is true is that MtE is *fragile in practice*: it leaves unauthenticated ciphertext reaching the parser, so constant-time MtE is a fight against the construction. The fix is to change the order, not to declare a theorem false.


It is a padding oracle; the downgrade is only the enabler that forces a client onto vulnerable SSL 3.0, whose pad bytes are unchecked by specification [@poodle_writeup] [@rfc6101]. The proof is TLS-POODLE (CVE-2014-8730), the same oracle against TLS stacks that copied the lax check, with no downgrade at all [@cve_2014_8730]. Frame POODLE as a padding oracle first, a downgrade second.


No. Bleichenbacher (1998) is an RSA PKCS #1 v1.5 oracle -- a different primitive with different arithmetic [@bleichenbacher1998]. Vaudenay (2002) is the first *CBC* instance [@vaudenay2002]. Bleichenbacher is the ancestor &quot;in spirit,&quot; because he first showed that a structural validity verdict is a decryption oracle, but calling his attack a CBC padding oracle confuses two branches of the family tree.


No. AES-GCM and ChaCha20-Poly1305 are counter-mode stream constructions with no block padding, and they verify the authentication tag before releasing any plaintext, so the Vaudenay atom has no surface to act on [@rfc8439]. GCM&apos;s own hazard is nonce reuse, which is a different problem -- the &quot;forbidden attack&quot; -- not a padding oracle [@bock_nonce_woot2016].


Only if authenticity is checked *first* and the unpadding is *also* constant-time. A constant-time compare on a MAC you verify *after* decrypting still leaves the padding check reachable, which is the whole class -- and a hand-written fast path can reintroduce the oracle anyway, as CVE-2016-2107 did, through a missing length check observable as a distinguishable alert rather than a timing leak [@cve_2016_2107]. The safe pattern is verify-then-decrypt, or an AEAD that gives you no other option.

&lt;p&gt;Every correction points at the same root: the padding was never the bug. Time to say the sentence the whole article was built to earn.&lt;/p&gt;
&lt;h2&gt;Decrypt, Then Confess&lt;/h2&gt;
&lt;p&gt;Return to that ASP.NET server in September 2010. AES-256 intact. The key never touched, never even approached. And the plaintext -- authentication tickets, &lt;code&gt;web.config&lt;/code&gt;, the site&apos;s master secrets -- gone, one byte at a time, because the server answered a question it should never have been asked before it authenticated the ciphertext [@rizzo_duong_woot2010].&lt;/p&gt;
&lt;p&gt;Line up the catalog and the shape is unmistakable. Bleichenbacher, Vaudenay, Canvel, ASP.NET, Lucky Thirteen, POODLE, CVE-2016-2107, Zombie POODLE: in every case the cipher did exactly what it promised, and the variable was always the &lt;em&gt;order of operations&lt;/em&gt;, leaking through a quieter channel each time it was silenced. A loud error became coarse timing became statistical timing became unchecked structure became a fix that re-created its own oracle. Five generations chased the bit; the bit kept escaping, because the precondition -- unauthenticated, malleable ciphertext reaching a padding check -- was never removed.&lt;/p&gt;
&lt;p&gt;The resolution the field finally deployed is a single move: verify authenticity &lt;em&gt;first&lt;/em&gt;. Encrypt-then-MAC proves it -- IND-CCA plus integrity of ciphertexts, the strongest of the three generic compositions, published two years &lt;em&gt;before&lt;/em&gt; Vaudenay&apos;s attack [@bellare_namprempre2000]. AEAD ships it as one primitive with no knob to misset, and TLS 1.3 makes it the only option a conformant stack can express [@rfc8446].&lt;/p&gt;
&lt;p&gt;This mirrors the running lesson of the series: Part 1&apos;s IND-CCA2 adversary is not a chalkboard abstraction but the exact adversary that walked out of that ASP.NET server, and Part 5&apos;s warning that confidentiality is not integrity is precisely the gap a padding oracle drives through.&lt;/p&gt;

When a ciphertext you did not create arrives, what does your receiver reveal about it before it has proven the ciphertext authentic? Encrypt-then-MAC and AEAD are the discipline of making the answer nothing.
&lt;p&gt;Carry that one question into every protocol you design or review. Drop Lucky Thirteen onto it, drop POODLE onto it, drop the next application-layer JWE oracle onto it: each is a non-empty answer, and the fix is always the same shape. The cipher was never the weak link, and no future cipher will be. That is why this is Part 6 of a field guide to &lt;em&gt;protocol design&lt;/em&gt;, not a chapter on block ciphers.&lt;/p&gt;
&lt;p&gt;&amp;lt;StudyGuide slug=&quot;padding-oracles-field-guide&quot; keyTerms={[
  { term: &quot;Padding oracle&quot;, definition: &quot;An attack class where a receiver&apos;s observable verdict on the padding of attacker-chosen ciphertext becomes a plaintext-recovery oracle, never touching the key.&quot; },
  { term: &quot;PKCS#7 padding&quot;, definition: &quot;Append n bytes each equal to n to fill the final block; a full extra block when already aligned (RFC 2315).&quot; },
  { term: &quot;CBC malleability&quot;, definition: &quot;Because each plaintext block is D_k(C_i) XOR C_(i-1), flipping a ciphertext byte flips the same plaintext byte deterministically.&quot; },
  { term: &quot;Chosen-ciphertext attack (IND-CCA2)&quot;, definition: &quot;An adversary that submits ciphertexts and learns from the receiver&apos;s reactions; a padding oracle is a deployed instance.&quot; },
  { term: &quot;Message authentication code (MAC)&quot;, definition: &quot;A keyed tag proving a message is authentic and unmodified; HMAC is the deployed instance.&quot; },
  { term: &quot;MAC-then-encrypt (MtE)&quot;, definition: &quot;MAC the plaintext, then encrypt; the receiver decrypts and unpads before verifying, so the padding verdict is observable by construction. SSL and TLS&apos;s original choice.&quot; },
  { term: &quot;Encrypt-then-MAC (EtM)&quot;, definition: &quot;MAC the ciphertext and IV, verify first, decrypt only authentic ciphertext; attains IND-CCA plus INT-CTXT. IPsec ESP and RFC 7366.&quot; },
  { term: &quot;INT-CTXT (integrity of ciphertexts)&quot;, definition: &quot;The adversary cannot produce any new ciphertext the receiver accepts; the property that closes the oracle.&quot; },
  { term: &quot;AEAD&quot;, definition: &quot;Authenticated Encryption with Associated Data: one primitive that verifies authenticity as part of decryption and returns nothing on failure (RFC 5116).&quot; },
  { term: &quot;Constant-time comparison&quot;, definition: &quot;A check whose running time does not depend on secret data or on where a difference occurs; the countermeasure Lucky Thirteen forced, whose hand-written implementation CVE-2016-2107 later showed to be fragile.&quot; }
]} /&amp;gt;&lt;/p&gt;
</content:encoded><category>cryptography</category><category>padding-oracle</category><category>cbc</category><category>tls</category><category>authenticated-encryption</category><category>aead</category><category>encrypt-then-mac</category><category>lucky-thirteen</category><author>noreply@paragmali.com (Parag Mali)</author></item></channel></rss>