Correct, Constant-Time, and Still Owned: A Field Guide to Side Channels, Faults, and Key Custody
Correct, constant-time crypto still leaks keys through timing, cache, speculation, and fault channels, and deployed systems break most often at key custody.
Permalink1. The Algorithm Was Correct. The Key Left Anyway.
In early 2024, researchers pulled private keys out of Apple's newest laptops -- not from buggy code, but from OpenSSL's Diffie-Hellman, Go's RSA, and the freshly standardized post-quantum algorithms CRYSTALS-Kyber and CRYSTALS-Dilithium, every one of them written to run in constant time [1]. Twenty-eight years earlier, Paul Kocher had recovered a different private key by doing nothing more exotic than watching how long the math took [2]. Same lesson, two eras: the proof modeled the algorithm; the attacker measured the machine.
This is the twenty-fifth and final part of a field guide that spent twenty-four installments proving, primitive by primitive, that the mathematics holds. Each of those proofs came with a silent condition attached. RSA is secure if you blind the exponent. Elliptic-curve signatures are unforgeable if the nonce is uniform and the scalar multiplication is data-independent. AES-GCM is authenticated if you never reuse a nonce and you compare tags in constant time. Twenty-four times, the phrase "if the caller does X" carried the whole argument. This part is about what happens when that "if" meets a physical machine and a running organization.
The math held; the machine and the operations handed the secret away. A cryptographic proof reasons about an algorithm -- an input/output relation. An attack happens to an implementation -- a physical, timed, power-drawing, speculating, operated process. The distance between those two things is the entire subject of this article.
That distance has a name, the abstraction gap, and it opens onto five surfaces where a correct algorithm sheds its secret. The first four are the exotic ones that make headlines: timing and cache channels, power and electromagnetic emanations, microarchitectural and transient-execution leaks, and induced faults. The fifth is the unglamorous one that ends most real incidents: the operational handling of keys and secrets themselves. The first four are where clever attackers earn conference papers. The fifth is where ordinary systems quietly die.
So this is not an encyclopedia of channels. It is an argument, backed by a catalog: every famous break in the record violates exactly one of four invariants -- a secret touched time, a secret touched an address, a fault skipped a check, or a key outlived or escaped its custody. Seen through those four invariants, the sprawling zoo of attacks collapses into a map that tells you where to spend your defensive budget.
To see why a correct algorithm leaks, though, you first have to see the gap the proofs never modeled. And it has a birthday.
2. Naming the Gap
Ask a cryptographer from 1990 what it means for a cryptosystem to be secure and you would get a clean answer: an adversary who sees only the inputs and outputs -- the ciphertexts, the signatures, the public keys -- cannot do better than guessing. The whole edifice of provable security, the IND-CPA and EUF-CMA definitions this series has used throughout, rests on that framing. The adversary is a mathematical object that submits queries and reads replies. It has no stopwatch, no oscilloscope, and no physical access to the box doing the computing.
That framing quietly omitted an entire dimension. A real computer does not merely map inputs to outputs. It takes time. It draws power. It radiates. It caches. It speculates. It can be made to stumble. None of those behaviors appears in the security definition, which means none of them was ever proven absent. The proof was silent about them not because they were safe, but because the model could not see them.
Information leaked by a computation's physical or microarchitectural behavior -- its running time, power draw, electromagnetic emanation, or cache state -- rather than by its defined input/output relation. A side channel is real even when the algorithm is mathematically perfect, because it lives in the machine, not the math.
There was already a Cold-War counterexample sitting in the open literature. In 1985, Wim van Eck published the first unclassified demonstration that a video display's electromagnetic emanations could be intercepted from a distance and its screen contents reconstructed with modest equipment [3]. Militaries had worried about "compromising emanations" under the codename TEMPEST for decades; van Eck moved the problem into public view. The lesson was blunt: computation emits exploitable physical signals whether or not the theory chooses to look. Van Eck's result is not itself a cryptographic attack -- it reconstructs a screen, not a key -- but it is the moment compromising emanations entered the open scientific record, which is why it anchors the side-channel lineage [3].
Diagram source
flowchart TD
A["Algorithm: an input/output relation the proof reasons about"] -->|realized as| B["Implementation: a physical, timed, operated process"]
A --> H["Output the proof models: ciphertext or signature"]
B --> C["Leak channel: time and cache addresses"]
B --> D["Leak channel: power and EM"]
B --> E["Leak channel: speculation and prefetch"]
B --> F["Leak channel: induced faults"]
B --> G["Leak channel: key custody and secret handling"] The eureka: time is an output
The gap got its name in 1996. Paul Kocher, then an independent cryptographer, made a single almost impudent observation: if the running time of a correct RSA or Diffie-Hellman exponentiation depends on the bits of the secret exponent, then merely timing the operation leaks the key [2]. No implementation bug is required. The code can be a flawless transcription of the textbook algorithm. The leak is in the fact that textbook modular exponentiation does more work for a one bit than for a zero bit.
The distance between an algorithm -- an input/output relation a proof reasons about -- and an implementation, which is a physical, timed, power-drawing, speculating, operated process. Every side-channel and key-management break in this article is a way of falling through that gap.
The canonical exponentiation loop makes the leak concrete. Square-and-multiply walks the secret exponent bit by bit: it squares at every step, and it performs an extra multiplication only when the current bit is one. The total running time therefore tracks the number of one bits, and finer measurements over chosen inputs recover the bits individually.
Diagram source
sequenceDiagram
participant E as Exponent bit
participant C as CPU
participant T as Wall-clock time
E->>C: current bit is 0
C->>C: square only
C->>T: short step
E->>C: current bit is 1
C->>C: square then multiply
C->>T: longer step
Note over C,T: total time tracks the number of one bits in the secret exponent Kocher's insight was not a new algorithm. It was the naming of a missing dimension. And once you accept that wall-clock time is an output the proof forgot to model, the same argument applies to every other physical observable: power, electromagnetic field, cache occupancy, speculative footprint, the very correctness of the result. The defensive program for the next thirty years becomes a single sentence: find each channel and remove the secret's influence over it. Paul Kocher bookends this whole field. He named the timing channel in 1996, co-invented Differential Power Analysis in 1999, and two decades later was a lead author of Spectre in 2018 [2][4][5]. One researcher's career traces the arc from a stopwatch to speculative execution.
That program organizes the rest of this article around those four invariants -- time, address, fault, custody. Hold them in mind. If time was an output the proof ignored, so was every other physical observable, and the attackers were only getting started.
3. The First Defenses, and Their First Breaks
Here is a fact that ought to feel counterintuitive: within one year of Kocher showing you could learn a key by measuring a correct machine, two teams showed you did not even need the machine to be correct. You could make it compute wrong on purpose and read the key out of the error.
The first generation of defenses was a scramble of point countermeasures, one patch per channel. Against timing, Kocher himself proposed blinding: before the secret operation, randomize the input so its running time carries no information about the true message. For RSA decryption you pick a random , multiply the ciphertext by , decrypt, and divide out . The identity that makes it work is
so multiplying the result by recovers while the timing now depends on the random blind, not the secret. Blinding was a proposal in 1996; it became a default only after David Brumley and Dan Boneh showed in 2003 that an OpenSSL RSA key could be recovered over a network by timing decryptions, ending the comfortable assumption that timing attacks needed a local probe [6]. OpenSSL turned blinding on by default in response: a theoretical nicety became a shipped mitigation the moment someone proved the threat was remote.
Breaking the machine on purpose
While the timing people were blinding, the fault people arrived. In 1997, Dan Boneh, Richard DeMillo, and Richard Lipton at Bellcore showed that a single faulty RSA signature computed with the Chinese Remainder Theorem optimization lets an attacker factor the modulus with one greatest-common-divisor computation [7]. The RSA-CRT speedup computes the signature modulo each prime separately and recombines the halves; corrupt just one half-computation -- a voltage glitch, a clock spike, a stray cosmic ray -- and the faulty signature comes out right modulo one prime but wrong modulo the other. Raising it to the public exponent should return the message ; instead shares exactly one of the two prime factors with , so hands you that factor. One bad signature, and the private key falls out.
An attack that induces a computational error -- through a voltage or clock glitch, an electromagnetic or laser pulse, or a software-triggered disturbance like Rowhammer -- so that the faulty output reveals the secret. The Bellcore result is the archetype: one incorrect RSA-CRT signature factors the modulus.
The same year, Eli Biham and Adi Shamir extended the idea from public-key to symmetric ciphers with Differential Fault Analysis: inject faults during a DES computation, difference the correct and faulty ciphertexts, and the secret key emerges [8]. The countermeasures wrote themselves in principle -- verify before you output a CRT signature, add redundancy and recompute, sense the glitch and refuse -- but each one added cost and each one had gaps.
Then, in 1999, Kocher returned with Joshua Jaffe and Benjamin Jun and opened the power rail. Simple and Differential Power Analysis showed that a device's instantaneous power draw depends on the data it switches, and that correlating power traces across many operations extracts a key even when the per-trace signal is buried in noise [4]. A smartcard running AES or RSA was leaking its key through its power pins to anyone with an oscilloscope and patience.
A statistical attack that correlates a device's measured power consumption across many operations with a hypothesized intermediate value, extracting the key even under heavy noise. Its countermeasures are masking (split secrets into random shares) and hiding (flatten or randomize the power signal).
The pattern is the argument. Each attack bred a defense -- blinding, verify-before-output, masking and hiding -- and each defense's gaps bred the next attack. This is whack-a-mole, and playing it one mole at a time was already visibly losing. Three structural cracks doomed the patch-by-patch approach: the channels were multiplying faster than the patches, each patch protected one primitive rather than the class, and the deepest crack was about to open -- the leak was preparing to jump from the value of the secret to the addresses the secret makes the processor touch.
4. The Channels Multiply
The next twenty years were a single sentence repeated in new dialects: the secret leaked through a channel the proof never modeled. What changed each time was the channel. Here is the whole family tree, and the timeline that carries it.
Diagram source
timeline
title Thirty years of the abstraction gap
1985 : van Eck EM emanations
1996 : Kocher timing attack
1997 : Bellcore RSA-CRT fault : Biham-Shamir DFA
1999 : Kocher-Jaffe-Jun DPA
2003 : Brumley-Boneh remote timing
2005 : Bernstein AES cache : Percival hyperthreading
2013 : Lucky Thirteen breaks TLS
2014 : Yarom-Falkner Flush and Reload : Rowhammer
2018 : Meltdown and Spectre
2020 : Minerva : TPM-Fail
2022 : Hertzbleed via DVFS
2024 : GoFetch : KyberSlash From the value to the address
The deepest move in the whole story is the one that happened between 2005 and 2014: the leak stopped being about the value of the secret and became about the addresses the secret makes the processor touch. Daniel Bernstein's 2005 cache-timing attack on AES showed that software using secret-dependent table lookups -- the standard fast AES implementation, indexing precomputed T-tables by key-dependent bytes -- leaks the key through cache timing, because which table entries end up cached depends on which ones the victim accessed [12].
The same year, Colin Percival showed that Intel Hyper-Threading's shared L1 cache lets one thread spy on a co-resident thread's access pattern, recovering RSA key bits from a concurrent OpenSSL process [13]. In 2006, Dag Arne Osvik, Adi Shamir, and Eran Tromer named and formalized the reusable primitives the field still uses [14]: Prime+Probe fills (primes) a cache set with the attacker's own lines, lets the victim run, then re-accesses (probes) those lines and times its own reload -- a slow reload reveals the victim evicted a line, so it needs no shared memory, while Evict+Time measures the victim directly.
A leak that recovers which memory addresses a victim touched by measuring shared-cache timing -- a hit is fast, a miss is slow. Flush+Reload is the sharpest variant: an attacker flushes a shared line, waits, then times the reload to learn whether the victim accessed it in the interval.
The technique reached its high-resolution form in 2014, when Yuval Yarom and Katrina Falkner delivered Flush+Reload: flush a shared cache line with clflush, wait, then time the reload to learn whether the victim touched it, all through the last-level cache shared across cores [15]. It recovered GnuPG RSA keys across cores and became the workhorse primitive behind the transient-execution attacks that followed. The countermeasure had crystallized into a principle, not a patch: never let a secret decide which address you touch.
From local to remote, and a flaw in the spec
Parallel to the address story ran the reach story. Brumley and Boneh had already made timing a network threat in 2003. Then, in 2013, Nadhem AlFardan and Kenneth Paterson published Lucky Thirteen, and it stung in a new way: the non-constant-time MAC-and-padding check in TLS and DTLS CBC-mode record processing is a timing oracle, and a man-in-the-middle can recover plaintext from it [16]. The wound was not one library's bug. The TLS specification mandated a MAC-then-pad-then-encrypt construction whose safe verification is genuinely hard to do in constant time. This was a spec-level flaw -- a reminder that "write it in constant time" is sometimes fighting the protocol design itself.
Below the instruction set
For a decade the discipline looked like it might be enough. Then, on 3 January 2018, the floor gave way. Meltdown and Spectre showed that transient execution -- the out-of-order and speculative machinery every fast CPU uses -- runs secret-dependent code paths that the architectural program never takes, and leaks their traces through the cache [17][5][18]. Meltdown let an unprivileged process transiently read kernel memory. Spectre poisoned branch prediction so that even correct, constant-time code speculatively executed a secret-dependent path and left it in the cache for a Flush+Reload probe to recover.
Instructions a CPU runs speculatively or out of order and then discards architecturally, so they never affect the program's official result -- but whose microarchitectural traces, chiefly cache state, survive and leak. Spectre and Meltdown proved that constant-time source code can be defeated by what the silicon does beneath it.
This was the qualitative break. Constant-time is a property of the instruction stream the programmer wrote. Speculation executes an instruction stream the programmer did not write, chosen by a branch predictor the attacker can train. The abstraction did not spring a leak; it was breached from underneath. Minerva (2020) is the miniature of the whole pattern: many ECDSA implementations leak the bit-length of the per-signature nonce during a non-constant-time scalar multiplication. Gather a couple thousand signatures, feed the tiny leaks to a lattice attack, and the long-term key falls out. On an Athena IDProtect smartcard the team needed about 2,100 signatures and recovered a P-256 key in minutes [19].
The machine keeps inventing channels
If Spectre broke constant-time from below through speculation, the next wave broke it from the side, through physics the instruction set never promised to hold still. In 2022, Yingchen Wang, Riccardo Paccagnella, and colleagues published Hertzbleed: modern x86 dynamic voltage and frequency scaling makes the CPU's clock frequency depend on the data being processed, so a program's wall-clock time depends on its data values -- turning a power side channel into a remote timing channel with no power measurement at all [20]. They extracted a full key from a constant-time implementation of the post-quantum candidate SIKE, remotely. SIKE died twice. Hertzbleed broke its constant-time implementation in 2022 through frequency scaling [20]. Weeks later, Wouter Castryck and Thomas Decru broke the mathematics of SIDH outright with a classical key-recovery attack, and SIKE was withdrawn [21]. The math break is out of this article's implementation scope, but the coincidence is a useful reminder that a primitive can fall from either direction.
Then, in 2024, came the break that opens this article. Apple's M-series CPUs contain a Data Memory-dependent Prefetcher (DMP) that inspects values loaded from memory and, when one "looks like" a pointer, dereferences it speculatively to prefetch what it points to [1]. That behavior is precisely the data-to-address coupling constant-time forbids. By crafting chosen inputs so that an intermediate value looks like a pointer only when a guess about a key bit is correct, the GoFetch team achieved end-to-end key extraction from constant-time OpenSSL Diffie-Hellman, Go RSA, and -- the part that should worry every protocol designer -- the post-quantum CRYSTALS-Kyber and CRYSTALS-Dilithium.
Diagram source
sequenceDiagram
participant A as Attacker
participant M as Victim memory
participant DMP as Data-memory prefetcher
participant Cache as Cache state
A->>M: craft input so a value looks pointer-like only when the key-bit guess is right
M->>DMP: value flows through a constant-time load
DMP->>DMP: value resembles a pointer, so dereference it
DMP->>Cache: prefetch pulls the pointed-to line into the cache
A->>Cache: time the reload to confirm the guess
Note over A,Cache: data influenced an address, the exact coupling constant-time forbids The same year, KyberSlash showed the discipline is unfinished in a humbler way. Several Kyber implementations contained a single line that divides a secret numerator by a public denominator; because integer division takes a data-dependent number of cycles on many CPUs, that one statement leaks ML-KEM secret bits into timing, sometimes exploitably [22]. Not an exotic prefetcher -- just a division, in a freshly standardized algorithm, that no one flagged.
The fault lineage, in parallel
Running alongside the leakage story the whole time was the fault story, and it too grew more remote. Classical fault injection meant physically glitching a chip with voltage, clock, electromagnetic, or laser pulses. In 2014, Rowhammer changed the threat model: repeatedly activating one DRAM row flips bits in physically adjacent rows without accessing them, a hardware fault triggered entirely in software [23]. Plundervolt then showed you could undervolt an Intel CPU from software to induce faults inside an SGX enclave, corrupting cryptographic computations from the operating system [24]. The fault, like the leak, had escaped the need for physical possession.
One lens for the whole catalog
Set every named break side by side and re-read each as exactly one violated invariant. The zoo becomes a table.
| Break (year) | Surface / channel | Root cause | Violated invariant | Lesson |
|---|---|---|---|---|
| Kocher timing (1996) [2] | Timing | Secret-dependent modular-exponentiation time | secret touched time | time is an unmodeled output; blind the operand |
| Bellcore RSA-CRT (1997) [7] | Fault | One faulty CRT signature, then a single GCD factors n | fault skipped a check | verify before output |
| DFA (1997) [8] | Fault | Fault plus differencing recovers the DES key | fault skipped a check | redundancy and verification |
| DPA (1999) [4] | Power | Data-dependent power draw over many traces | secret touched power | masking and hiding |
| Brumley-Boneh (2003) [6] | Remote timing | OpenSSL RSA decryption timing over a network | secret touched time, remotely | blinding on by default |
| Bernstein AES cache (2005) [12] | Cache | Secret-indexed T-table lookups | secret touched an address | constant-time, AES-NI, bitslicing |
| Percival (2005) [13] | Cache under SMT | Shared L1 under Hyper-Threading | secret touched an address | co-residency threat model |
| Lucky Thirteen (2013) [16] | Timing | Non-constant-time MAC-and-pad check in TLS CBC | secret touched time | constant-time MAC verification, a spec fix |
| Flush+Reload (2014) [15] | Cache | Flush plus reload on shared last-level cache | secret touched an address | high-resolution shared-cache primitive |
| Rowhammer (2014) [23] | Fault, software-triggered | DRAM disturbance flips adjacent-row bits | fault skipped a check, no physical access | software can induce hardware faults |
| Meltdown and Spectre (2018) [17][5] | Transient execution | Speculation runs secret-dependent paths, leaks via cache | secret touched an address below the ISA | constant-time broken beneath the ISA |
| Minerva (2020) [19] | Timing | Non-constant-time ECDSA nonce bit-length plus lattice | secret touched time | constant-time scalar multiplication |
| TPM-Fail (2020) [25] | Timing | Secret-dependent ECDSA time in a certified TPM | secret touched time | certified is not side-channel-safe |
| Hertzbleed (2022) [20] | Power to remote timing via DVFS | Frequency scaling makes wall-time data-dependent | secret touched time, remotely | power channels can go remote |
| GoFetch (2024) [1] | Cache via DMP prefetcher | Prefetcher dereferences pointer-like data | secret touched an address below the ISA | constant-time unfinished, reaches PQC |
| KyberSlash (2024) [22] | Timing via division | Secret numerator over public denominator, variable-time divide | secret touched time | constant-time incompletely applied to ML-KEM |
| Storm-0558 (2023) [26] | Operational key custody | Unrotated, software-resident 2016 signing key | a key outlived and escaped its custody | custody, not math |
You can only close the channels you model, and the machine keeps inventing new ones.
Read the catalog through those four invariants and the pattern snaps into focus. Three of the four -- time, address, fault -- are the exotic surfaces, and they share one defense strategy. The fourth is different in kind, and it is where deployed systems actually break. That split is the breakthrough.
5. Two Breakthroughs That Arrive Together
After a decade of one-patch-per-channel, the field found the single rule that closes an entire family of channels at the source. And in almost the same breath, it learned the rule was necessary but not sufficient -- which forced a second, operational breakthrough to sit beside the first. The two arrive together because each is the other's admission of limits.
The software breakthrough: constant-time as a discipline
State it precisely, because the precision is the whole point. Constant-time code has no secret-dependent branches, no secret-dependent memory indices, and no data-dependent variable-time instructions. That is it. Crucially -- and this is the single most misunderstood idea in the field -- constant-time does not mean fixed-duration execution. It means data-independent execution: the same instruction stream and the same sequence of memory addresses regardless of the secret's value. A routine can finish in wildly different absolute times on different inputs and still be constant-time, so long as the variation never depends on a secret.
Code whose instruction sequence and memory-address trace do not depend on secret values. It is NOT fixed-duration execution. The rule has three clauses: no branch on a secret, no memory index computed from a secret, and no variable-latency instruction (early-exit compare, data-dependent division) applied to a secret.
In practice the discipline replaces three forbidden patterns. A secret-dependent if becomes a branchless select computed with masks and arithmetic. A secret-indexed table lookup, table[secret], becomes a full scan of the table with a constant-time select, or is dissolved entirely by bitslicing the algorithm into boolean operations that touch no data-dependent address [12]. A variable-time operation on a secret -- an early-exit memcmp on a MAC, a division with a secret numerator -- is banned outright. Whole primitives were redesigned to make the discipline natural: Curve25519's Montgomery ladder runs the identical steps for every bit of the scalar, so there is no secret-dependent path left to leak.
The comparison case is worth seeing in code, because it is the exact mistake behind Lucky Thirteen and a hundred MAC-verification bugs.
// Early-exit compare: returns as soon as two bytes differ.
// The iteration count reveals how long a prefix matched -- a timing leak.
function earlyExitEqual(a, b) {
if (a.length !== b.length) return false;
for (let i = 0; i < a.length; i++) {
if (a[i] !== b[i]) return false; // stops at the first difference
}
return true;
}
// Constant-time compare: always scans every byte, accumulating differences.
// Running time depends only on length, never on the secret contents.
function constantTimeEqual(a, b) {
if (a.length !== b.length) return false;
let diff = 0;
for (let i = 0; i < a.length; i++) {
diff |= a[i] ^ b[i]; // no early exit, no branch on the data
}
return diff === 0;
}
const tag = [1, 2, 3, 4, 5, 6, 7, 8];
const nearMiss = [1, 2, 3, 4, 5, 6, 7, 9]; // matches 7 of 8 bytes
const farMiss = [9, 2, 3, 4, 5, 6, 7, 8]; // differs at the very first byte
// earlyExitEqual iterates 8 times for nearMiss but only once for farMiss:
// that difference is the leak an attacker times to forge a tag byte by byte.
console.log('constant-time, near miss:', constantTimeEqual(tag, nearMiss));
console.log('constant-time, far miss :', constantTimeEqual(tag, farMiss)); Press Run to execute.
The other half of the breakthrough is that the property became checkable, and even provable. Oscar Reparaz and colleagues built dudect, which runs a routine on fixed-versus-random secret inputs and applies a Welch t-test: a stable t-statistic past the usual threshold flags a data-dependent timing leak, while a genuinely constant-time routine stays flat [27]. On the formal side, ct-verif encodes constant-timeness as a property over pairs of executions differing only in secrets and discharges it mechanically [28]. Constant-time stopped being a matter of the author's vigilance and became something a machine could test in CI or prove outright.
For the physical surface -- power, electromagnetic, glitch -- the corresponding discipline is masking, and it too became principled rather than ad hoc.
Masking splits every secret intermediate into random shares so that any probed values are statistically independent of the secret; the computation runs on shares. Threshold implementations add non-completeness and uniformity so the construction stays secure even under hardware glitches, which transiently leak the unmasked value [29].
But masking closes only the physical channel, and constant-time closes only the modeled ISA-level channels. Spectre, Hertzbleed, GoFetch, and KyberSlash all live below the abstraction the discipline reasons about. You can be perfectly constant-time in the instruction stream you wrote and still leak, because the silicon runs an instruction stream you did not write. That is the wall the software breakthrough hits -- and it is the reason for the second breakthrough.
The operational breakthrough: key custody as an architecture
If you can never prove the whole machine leak-free, stop trying to eliminate leakage and instead bound what a leak can cost. That is the pivot. Concretely: put the root key inside a hardware boundary that generates and uses it without ever exporting it, and reach your data through an envelope.
A tamper-resistant boundary -- a dedicated appliance, a cloud Managed HSM, a TPM, or an embedded secure element -- that generates and uses keys without releasing them in plaintext. Software sends in a request (sign this, unwrap that) and gets back a result, never the key bytes. FIPS 140-3 validates such modules, including some non-invasive side-channel requirements.
A pattern in which a cheap Data-Encryption Key (DEK) encrypts the bulk data, and a Key-Encryption Key (KEK) that never leaves the HSM or KMS wraps the DEK. The wrapped DEK is stored right beside the ciphertext; only the KMS can unwrap it. Standardized authenticated key wrapping (NIST SP 800-38F) supplies the wrap operation [30].
The economics are what make it deployable. A hardware module is slow and expensive, so you do not run all your data through it -- you run one small wrap and unwrap through it, and let a cheap symmetric DEK do the bulk work. A leaked DEK exposes only the data it wrapped; the KEK, and therefore every other DEK, stays safe inside the boundary. Rotation becomes automatic and its blast radius bounded. This is the move from "make the code not leak" to "make a leak not matter," and it maps directly onto how NIST SP 800-57 tells you to think about key lifecycles and crypto-periods [31].
Diagram source
flowchart LR
D["Plaintext data"] -->|"encrypt with"| DEK["Data-Encryption Key (DEK)"]
DEK -->|"wrap with KEK"| WD["Wrapped DEK, stored beside ciphertext"]
KEK["Key-Encryption Key (KEK)"] --> HSM["Inside HSM or cloud KMS, never exported"]
WD -.->|"ask KMS to unwrap"| HSM
HSM -.->|"plaintext DEK, policy permitting"| App["Application decrypts the data"] The junction
The two breakthroughs are two chains of the same story, and they meet at one move. Chain A drives the code toward not leaking, generation by generation, until it hits the microarchitectural wall. Chain B accepts that wall and shrinks what any residual leak is worth. The thesis of this whole series is the junction of the two: the math held; Chain A closes the channels you can model, and Chain B bounds the cost of the ones you cannot.
Diagram source
flowchart TD
A0["A0: correctness is enough"] --> A1["A1: point patches, blinding, verify, masking"]
A1 --> A2["A2: constant-time discipline"]
A2 --> A3["A3: verified plus hardware-assisted constant-time"]
B0["B0: keys as data, hardcoded or in config"] --> B1["B1: OS or software keystore"]
B1 --> B2["B2: TPM, HSM, secure element"]
B2 --> B3["B3: cloud KMS, envelope, automatic rotation"]
A3 --> T["Thesis: the math held. Close the modeled channels, bound the cost of the rest"]
B3 --> T Defense evolves from "make the code not leak" to "make a leak not matter." The first is Chain A and it is necessary. The second is Chain B and it is what saves you when Chain A inevitably has a gap the machine invented last week.
Both breakthroughs are engineering realities you can deploy today: verified constant-time libraries on one side, cloud KMS with envelope encryption and automatic rotation on the other. So what does the 2024 to 2026 state of the art actually look like when you ship both at once -- and where is it still losing?
6. The State of the Art, 2024 to 2026
There is no single best implementation. There are two parallel frontiers, and the 2024-to-2026 practitioner ships both at once. Call them Chain A, leakage defense, and Chain B, key custody.
Chain A: the leakage-defense frontier
At the base sits the constant-time-by-construction library. libsodium, Google's BoringSSL, Thomas Pornin's BearSSL, and Rust's ring obey the three-clause rule everywhere a secret is touched. This is table stakes now, not a differentiator -- if a general-purpose crypto library is not constant-time by default in 2026, it is not a serious library.
Above that sits the highest-assurance floor: formally verified constant-time crypto, where a machine-checked proof replaces human review.
Cryptographic code whose secret-independence is established by a machine-checked mathematical proof, not by review or testing. HACL* and EverCrypt (written in a subset of F*) and the Jasmin and libjade toolchain (with EasyCrypt) are the two production lineages; the proof itself is the constant-time argument, discharged once and re-checked automatically [32].
This is not a laboratory curiosity. HACL* ships verified Curve25519 in Mozilla Firefox through NSS, and a formally verified Curve25519 rode into the Linux kernel alongside WireGuard; verified SHA-3 and HMAC from the same project ship inside CPython [32]. Proof-carrying crypto is in the browser and the operating system you are reading this on.
Verifying and testing the property is its own layered toolchain, and no single tool dominates. A 2023 systematic evaluation quantified the trade-offs across real libraries [33]; the shape of the field is a ladder from cheap-and-empirical to expensive-and-proven.
| Tool | Technique | Level analyzed | Guarantee | Catches compiler-introduced leaks | Effort | Microarchitecture |
|---|---|---|---|---|---|---|
| dudect [27] | Welch t-test on timing | Black-box, runtime | Empirical, tested paths | Only if exercised | Very low, CI drop-in | Not modeled |
| ctgrind / TIMECOP | Dynamic taint over Valgrind | Binary, dynamic | Empirical, executed paths | Yes, when observed | Low | Not modeled |
| ct-verif [28] | Formal self-composition | LLVM IR | All paths at the IR | No, runs before compilation | Medium, needs annotations | Not modeled |
| Binsec/Rel [34] | Relational symbolic execution | Final binary | All paths, bounded depth | Yes | High, setup and path explosion | Not modeled |
| HACL* / Jasmin [32] | Proof in F* or EasyCrypt | Source down to C or assembly | Whole-program theorem | Verified assembly, yes | Very high, proof engineering | Not modeled |
Read the last column: every tool shares the same blind spot. None models the microarchitecture, because none can -- a limit we return to in Section 8. The empirical tools like dudect are the ones you actually gate CI with, and the Welch t-test at their heart is simple enough to see in a few lines.
// Simulate timing samples for two input classes: a "fixed" secret and "random" secrets.
// A variable-time operation reacts differently to the two classes; a constant-time one does not.
function mean(xs) { return xs.reduce((s, x) => s + x, 0) / xs.length; }
function variance(xs, m) {
return xs.reduce((s, x) => s + (x - m) * (x - m), 0) / (xs.length - 1);
}
// Welch t-statistic for two samples with unequal variance.
function welchT(a, b) {
const ma = mean(a), mb = mean(b);
const va = variance(a, ma), vb = variance(b, mb);
return (ma - mb) / Math.sqrt(va / a.length + vb / b.length);
}
function samples(n, base, classShift) {
const out = [];
for (let i = 0; i < n; i++) out.push(base + Math.random() * 5 + classShift);
return out;
}
const N = 5000;
// Variable-time op: the random class runs a few units longer on average, a real leak.
const leakFixed = samples(N, 100, 0), leakRandom = samples(N, 100, 8);
// Constant-time op: both classes share one distribution, no leak.
const ctFixed = samples(N, 100, 0), ctRandom = samples(N, 100, 0);
console.log('variable-time absolute t:', Math.abs(welchT(leakFixed, leakRandom)).toFixed(1), 'past 4.5 threshold, flagged');
console.log('constant-time absolute t:', Math.abs(welchT(ctFixed, ctRandom)).toFixed(1), 'below 4.5, clean'); Press Run to execute.
Beneath the software sits hardware backing. Intel's Data Operand Independent Timing Mode (DOITM) and Arm's Data Independent Timing (DIT) bit promise that an enumerated set of instructions runs with data-independent latency, re-establishing at the silicon level the guarantee the source could only assert [35][36]. But their coverage is exactly their limit. The lists are vendor-specific and enumerated, and they pointedly exclude integer and floating-point division and square root -- which is why KyberSlash's secret division leaks -- and they do not model the prefetcher or frequency scaling, which is why GoFetch and Hertzbleed operate outside their guarantee entirely [22][1].
For the physical surface -- power, electromagnetic, glitch -- masking and threshold implementations plus on-die fault sensors remain the answer, and the live research frontier is masking the post-quantum algorithms at practical cost.
Chain B: the key-custody frontier
The operational state of the art is cloud KMS plus envelope encryption plus automatic rotation. AWS KMS, Azure Key Vault and Managed HSM, and Google Cloud KMS all keep a small number of Key-Encryption Keys inside a hardware boundary and wrap an unbounded number of cheap Data-Encryption Keys under them, using the authenticated key-wrapping of NIST SP 800-38F and the lifecycle discipline of NIST SP 800-57 [30][31]. AWS puts a concrete number on the crypto-period: keys that AWS manages rotate on a required schedule of every year, approximately 365 days, and customer-managed keys support optional automatic rotation on a period you choose [37]. Rotation is O(1) to request; the hard part is re-wrapping and inventorying what each key protects.
The current U.S. standard for validating cryptographic modules, superseding FIPS 140-2. Unlike its predecessor's reputation, FIPS 140-3 explicitly addresses non-invasive security -- that is, side-channel resistance -- among its requirements, across four ascending security levels with increasing physical tamper response [38].
The third pillar is the least technical and the most often fatal: secrets management and non-human-identity hygiene. Application secrets -- API keys, database credentials, tokens, signing keys -- must stay out of code, config files, container images, and CI logs. OWASP made cryptographic-usage and keying failures the number-two web-application risk in 2021, and the 2025 OWASP Non-Human Identities Top 10 put improper offboarding (NHI1) and secret leakage (NHI2) at the very top of the list, with long-lived secrets also named (NHI7) [39][40]. The tooling exists -- secrets managers that issue short-lived attested credentials, CI secret-scanning, workload federation -- but it demands organizational discipline no library can supply.
The concrete arc: Storm-0558 to Managed HSM
The clearest picture of Chain B's state of the art is the failure that forced it and the remedy that answered it. In 2023, the threat actor Storm-0558 forged Microsoft consumer authentication tokens using a 2016 signing key that was software-resident, held outside any HSM, and had never been rotated; a token-validation flaw let that consumer key mint enterprise tokens, and the actor downloaded roughly 60,000 State Department emails (about ten accounts), part of an intrusion that reached around two dozen organizations [26][41]. The U.S. Cyber Safety Review Board judged the intrusion preventable. No cipher was broken. A key simply outlived and escaped its custody.
Microsoft's remedy, under its Secure Future Initiative, is the Chain B playbook made concrete: the MSA and Entra ID signing keys moved into Azure Managed HSM with automatic rotation, and the signing service itself moved into confidential-computing VMs [42]. Put the root key in hardware, use it without exporting it, rotate it automatically. That is the entire prescription, and it is exactly what was missing in 2016.
The honest caveat closes the section. Hardware custody is not a magic floor. A 2025 taxonomy catalogs how cloud HSM and TPM custody actually fails in practice, and TPM-Fail already showed that certified modules leak keys through timing [43][25]. Hardware moves the trust boundary; it does not remove it.
Custody checks you can run on your own systems
A few commands turn the abstract advice into an audit. Confirm a KMS key actually rotates with aws kms get-key-rotation-status --key-id KEY_ID. List which principals can call decrypt on it by reading the key policy with aws kms get-key-policy --key-id KEY_ID --policy-name default. Scan a repository's history for committed secrets with gitleaks detect --source . or trufflehog filesystem .. Gate a C library's tag comparison in CI by building it under valgrind with the TIMECOP macros, or by wiring a dudect harness around the routine. None of these is exotic; all of them catch the mistakes that end real incidents.
You now know the tools on both frontiers. The hard part is the two decisions nobody hands you: where do you put the defense, and where do you put the key?
7. Where Do You Put the Defense, and Where the Key?
Every choice in this field trades assurance against cost, portability, and threat model. Two decision axes organize the whole thing, and the good news is that within each axis the options mostly compose rather than compete.
Axis one: defense placement
The four leakage defenses are not rivals fighting for one slot. They stack. Ship verified constant-time as the floor, enable hardware data-independent-timing as backing, and add masking only when the adversary can physically hold and instrument the device.
| Approach | Assurance | Runtime cost | Portability | Defends against | Blind to | Best suited for |
|---|---|---|---|---|---|---|
| Software constant-time | Review-level | Near-constant factor | High, any CPU | Timing and cache | Microarchitecture: Spectre, DVFS, DMP | Every deployment, the floor |
| Verified constant-time [32] | Machine-checked proof | Same as software | High, generated C | Timing and cache, proven | Below the verified boundary; microarchitecture | Long-lived high-assurance primitives |
| Hardware DIT / DOITM [35][36] | Vendor guarantee, listed instructions | Near zero for covered instructions | Low, vendor-specific | Operand timing of listed instructions | Prefetcher, frequency scaling, division | Defense-in-depth beneath software |
| Masking / threshold [44] | Provable order-d in the probing model | Quadratic in the order | Hardware-oriented, costly in software | Power, EM, glitch faults | Higher-order analysis; cost | Physical-possession adversary |
The table's lesson is the composition, not any single winner. Each defense covers what the one above it cannot; masking, in particular, is the only thing that helps once an attacker owns the silicon, and you pay for it by the square of the order. None of them closes the microarchitectural channels alone -- which is the structural reason Chain B has to exist.
Axis two: the key-custody spectrum
Custody is a ladder, and the single most useful skill in this whole article is matching the rung to the adversary and the asset value.
| Rung | Key location | Exportable? | Blast radius on host compromise | Cost / latency | Use when | Anchor |
|---|---|---|---|---|---|---|
| 0 | Hardcoded, config, env var, repo | Yes, in plaintext | Total, the key is the leak | None | Never, an anti-pattern | Storm-0558 [26] |
| 1 | OS or software keystore | Yes, to root or admin | High | Low | Low-value single-host secrets | OWASP A02 [39] |
| 2 | TPM or secure element | No, use without export | Low, but see TPM-Fail | Low to medium | Device-bound keys, measured boot | TPM-Fail [25] |
| 3 | Cloud KMS plus envelope | KEK no, DEK wrapped | Bounded to unwrapped DEKs | One KMS round-trip per DEK | Data at rest at scale | AWS KMS [37] |
| 4 | Dedicated or Managed HSM | No | Very low | Higher | Root, CA, code-signing, payment keys | FIPS 140-3 [38] |
| 5 | Threshold or MPC custody | No single holder | Requires several compromises | Highest, protocol rounds | No single point of trust, see Part 22 | NIST SP 800-57 [31] |
Here is the capstone's punchline, sitting in one table. The exotic Chain-A attacks -- a timing leak against a certified TPM at rung 2, say -- are real but rare. The overwhelming majority of real-world breaks cluster at rungs 0 and 1: keys hardcoded, sprawled across config and CI logs, software-resident, and never rotated. Storm-0558 was a rung-0 failure with a nation-state on the other end. The state-of-the-art move for almost everyone is simply to climb to rung 3 or 4 with automatic rotation, and to accept the custody regress that waits at the top.
"Certification has failed to protect the product against an attack that is considered by the protection profile." -- Moghimi et al., the TPM-Fail disclosure [25]
These tables tell you what to reach for. But can leakage ever be eliminated, or are we only ever bounding its cost? The honest answer is uncomfortable, and it comes in two parts.
8. Can Leakage Be Eliminated?
The field owes you two very different answers. One is a clean, provable bound. The other is an honest impossibility that is not a theorem at all but a structural fact about how computers are specified.
The masking cost curve is a real bound, and it is not a wall
For the physical channel, the mathematics is unusually crisp. In the Ishai-Sahai-Wagner probing model, protecting a secret against an adversary who can observe up to internal wires requires at least random shares, and that bound is tight -- you cannot buy order- security with fewer [44]. Securely masking a single multiplication in that model costs in circuit size and randomness. So security grows linearly in the order while cost grows quadratically: doubling your resistance roughly quadruples the work.
Under a physically realistic noisy-leakage model, the picture gets better in one specific way. Duc, Dziembowski, and Faust proved that masking's security amplifies exponentially in the number of shares, provided each share carries enough noise, by reducing noisy leakage back to the clean probing model [45]. Put the two results together and you get the defining shape of the whole physical-defense story: leakage is a cost curve, not a wall. You can push the attacker's required number of traces arbitrarily high by paying and ensuring noise. You can never push it to zero.
Whole-machine constant-time is an impossibility, not a theorem
For the timing and cache channels, there is no comparable happy bound, and the reason is almost embarrassingly simple. Constant-time cannot be guaranteed at the microarchitectural level because the instruction set architecture does not specify timing. A constant-time proof is a statement about ISA-visible behavior. The silicon underneath is free to introduce data-dependence the ISA never promised to forbid, and it repeatedly does.
The ISA does not specify timing, so a constant-time proof only constrains architecture-visible behavior. You can only close the channels you model, and the set of channels is open-ended and adversary-discovered. Speculation, frequency scaling, and data-memory-dependent prefetching each reopened it after the proof was done.
This is why Spectre, Hertzbleed, GoFetch, and KyberSlash are not a series of fixable bugs but a series of demonstrations of the same structural gap. Each exploited a microarchitectural behavior -- speculation, DVFS, a prefetcher, variable-time division -- that no source-level or even ISA-level guarantee covered. There is no proof that a real machine leaks nothing through timing, and, because the ISA is silent on timing, none is possible with today's hardware contracts. The gap is not small and closing. It is structurally open until a complete, cross-vendor data-independent-timing contract exists, which DIT and DOITM only partially provide.
The custody regress
The operational half has its own impossibility, and it is the reason this article lands on key custody rather than on some final leak-proof coprocessor. Someone must ultimately hold a root secret. Hardware roots -- HSMs, TPMs, Managed HSMs -- move the trust boundary inward and defend it far better, but they do not remove it, and that smaller boundary has its own documented failure modes, from TPM-Fail's timing leak to the 2025 taxonomy of cloud HSM and TPM failures [25][43]. There is no keyless bottom. There is only a smaller, better-defended custodian, and certification of that custodian is necessary but never sufficient.
Hardware moves the trust boundary; it does not remove it.
So perfection is off the table on both halves. What replaces it is not despair but a different target.
The purely theoretical program of leakage-resilient cryptography pushes on the same questions from the model side, but relative to deployed masking and hardware custody it remains largely academic. If perfection is unreachable, the frontier turns into a list of concrete engineering fights -- and, refreshingly, they are winnable one channel at a time.
9. The 2026 Frontier
Name the open problems precisely and the field stops looking like whack-a-mole and starts looking like a research agenda. Each of these is calibrated engineering, not undetectable magic.
A complete microarchitectural timing contract. DIT and DOITM enumerate data-independent instructions, but they are vendor-specific and exclude division, square root, the prefetcher, and frequency scaling [35][36]. Every constant-time proof is therefore conditional on an unmodeled microarchitecture, which is exactly the seam GoFetch and Hertzbleed pried open. The open problem is an ISA-level guarantee that extends a constant-time proof below the ISA, across vendors.
Speculative constant-time at scale. Spectre-class transient execution breaks architecturally constant-time code from beneath [5]. Retpoline, LFENCE barriers, and Speculative Load Hardening are point defenses with real, often double-digit-percent overhead [46]. A scalable, low-cost compiler that guarantees data-independence even under speculation, for whole libraries, does not yet exist. The community conjecture is that without hardware speculation-taint tracking, a software-only speculative-constant-time pass cannot be both complete and cheap. You either pay in overhead or accept coverage gaps.
Whole-library verification through the compiler. Compiler lowering reintroduces leaks that IR-level proofs miss, which is the entire motivation for binary-level tools like Binsec/Rel [34]. Today we have per-primitive proofs and per-binary bounded checks; 2024 work such as CT-Prover targets the scalability wall, and benchmarks map the tool terrain [47][33]. A push-button, whole-library, binary-level verifier usable by non-experts remains out of reach -- and usability studies show even experts misconfigure the current tools.
Constant-time and fault-resistant post-quantum code. KyberSlash and GoFetch proved the discipline is unfinished for the freshly standardized lattice schemes [22][1]. Shipping ML-KEM and ML-DSA implementations that are simultaneously constant-time, masked against physical attack, and fast across diverse CPUs -- and proving it -- is an active frontier, because the arithmetic-heavy PQC code has larger, fresher leakage surfaces than classical crypto.
Secretless architectures and minimal key lifetimes. The dominant real-world failure surface is long-lived, sprawled, unrotated secrets [40]. Rotation, workload attestation, and short-lived derived credentials shrink the custody surface, but the custody regress forbids a truly secretless bottom. The open question is quantitative: how small can the irreducible custodian be made?
Post-quantum key management at fleet scale. Larger keys, hybrid classical-and-PQ modes, and harvest-now-decrypt-later urgency make the key lifecycle harder precisely as it becomes critical. Zero-downtime migration across a whole estate, with a cryptographic bill of materials for inventory, anchored on the lifecycle discipline of NIST SP 800-57, is an open operational problem handed off to the crypto-agility part of this series [31].
The research agenda is clear. The deployment agenda is clearer still. Here is what to do on Monday.
10. What To Do on Monday
Everything above collapses into a handful of rules you can apply without a research budget.
The constant-time checklist
Anything that touches a secret gets three prohibitions: no branch on it, no memory index computed from it, no variable-time operation applied to it -- and "variable-time" specifically includes integer division and early-exit comparison. Test the result with dudect or TIMECOP in CI, prefer a verified library over your own code, and re-check after every toolchain upgrade, because compilers betray branchless code back into branches. That single decision rule fits in a diagram.
Diagram source
flowchart TD
S["A value in your code"] --> Q{"Does it touch a secret?"}
Q -->|no| OK["Ordinary code is fine"]
Q -->|yes| R["Forbid all three"]
R --> B["No branch on it"]
R --> M["No memory index from it"]
R --> V["No variable-time op: no divide, no early-exit compare"] Threat-model routing for leakage defense
Match the defense to the adversary. Everyone starts from the same floor and adds as the threat model widens.
| Adversary | Add to the floor | Because |
|---|---|---|
| Remote network | Verified or constant-time crypto, constant-time MAC checks, no padding oracles | This is the floor for everyone; it closes Lucky Thirteen and the timing family |
| Co-tenant, shared cache (cloud) | Cache-hardening, per-tenant isolation, hardware CT where honored | Prime+Probe and Flush+Reload cross tenant boundaries |
| Physical possession (smartcard, wallet, IoT) | Masking or threshold implementations, fault sensors, a secure element | The attacker owns and can instrument the silicon |
| Assume speculation, DVFS, DMP exist | Treat CT as necessary-not-sufficient; isolate secrets across process and VM boundaries; keep key lifetimes short | These channels live below the ISA and cannot be closed in source |
The key-management playbook
The custody decision is a lookup table keyed on asset and threat. A low-value single-host secret belongs in the OS keystore. A device-bound key with measured boot belongs in a TPM or secure element. Lots of data at rest belongs behind cloud KMS with envelope encryption and automatic rotation [37]. A root, certificate-authority, code-signing, or payment key belongs in a dedicated or Managed FIPS 140-3 HSM [38].
A key that must survive with no single point of trust belongs in threshold or MPC custody, the subject of Part 22. And any application secret belongs in a secrets manager issuing short-lived attested credentials, with CI secret-scanning behind it -- never hardcoded, always rotated, and stale non-human identities always offboarded [40][31].
One caution about rotation, because it is oversold. Rotation bounds the blast radius of a leaked key -- it caps how much a single compromised key is worth and for how long -- but it is not a substitute for custody. Storm-0558's key was both never rotated and software-resident; rotation alone would have shortened the exposure window, but keeping the key in an HSM is what stops the export in the first place. Rotate and custody; the two do different jobs.
The common-misuse catalog
Most real leaks are not exotic. They are the same dozen mistakes, made again. One misuse deserves special note because the fix for a different problem creates it: deterministic ECDSA (RFC 6979) removes the nonce-RNG risk [48], but a fault during signing then yields two signatures on the same nonce, which recovers the key [49]. Hedged signing -- mixing in fresh randomness -- restores fault resistance without reopening the RNG risk. Part 17 treats this trade in full.
| Misuse | Why it leaks | Fix |
|---|---|---|
memcmp on a MAC or tag | Early-exit timing oracle, the Lucky Thirteen class [16] | Constant-time compare (CRYPTO_memcmp, sodium_memcmp) |
| Secret-indexed table lookup | Cache-timing leak of the accessed address [12] | AES-NI or bitslicing |
| Secret-dependent division or branch | Variable-time; division is not a data-independent instruction [22] | Replace with multiply, mask, or branchless select |
| Compiler betrayal | Optimizer restores a branch, or elides a memset that scrubs a secret | Verify the binary, use secure-zero intrinsics, re-check after upgrades |
| Skipping RSA-CRT verification | Reopens the Bellcore fault; one bad signature factors n [7] | Verify the signature before releasing it |
| Home-rolled constant-time | Assume it is not constant-time until a tool proves otherwise | dudect or TIMECOP, plus a verified library |
| Hardcoded or sprawled secrets | Rung 0; the key is the leak [26] | Secrets manager, rotation, offboarding |
| Reusing a KEK as a DEK | Couples bulk data directly to the root key | Keep DEK and KEK distinct; wrap, do not encrypt with the KEK |
| Non-constant-time post-quantum code | Fresh, unhardened leakage surface [1] | Patched, verified PQC libraries |
The envelope pattern at the bottom of the custody advice is worth seeing in code, because the blast-radius argument is the whole point.
// Illustrative only. Real systems use AES-GCM to encrypt and AES-KW to wrap,
// and the KEK lives inside an HSM or cloud KMS that performs wrap and unwrap on request.
function xorCrypt(bytes, key) { return bytes.map((b, i) => b ^ key[i % key.length]); }
function randomKey(n) { return Array.from({ length: n }, () => Math.floor(Math.random() * 256)); }
// The KEK never leaves this boundary; software only sends wrap and unwrap requests.
const KEK = randomKey(16);
function wrap(dek) { return xorCrypt(dek, KEK); } // stands in for a KMS wrap call
function unwrap(wd) { return xorCrypt(wd, KEK); } // stands in for a KMS unwrap call
// Encrypt: fresh DEK per object, encrypt the data, wrap the DEK, store the wrapped DEK beside it.
const data = [72, 105, 33];
const dek = randomKey(16);
const ciphertext = xorCrypt(data, dek);
const wrappedDek = wrap(dek); // this, not the plaintext DEK, is stored with the ciphertext
// Decrypt: ask the boundary to unwrap the DEK, use it, discard it.
const plaintext = xorCrypt(ciphertext, unwrap(wrappedDek));
console.log('round-trips:', plaintext.join(',') === data.join(','));
// Blast radius: a leaked wrappedDek without the KEK reveals nothing; a leaked DEK exposes only its one object. Press Run to execute.
11. Seven Beliefs That Ship to Production and Get People Owned
Frequently asked questions
Constant-time means always the same time, right?
No, and this is the single most consequential misunderstanding in the field. Constant-time means data-independent execution: the same instruction stream and the same memory-address trace regardless of the secret's value. It does not mean fixed duration. A routine can run for different absolute times on different inputs and still be constant-time, as long as the variation never depends on a secret. Conversely, padding a function to a fixed wall-clock time does not make it constant-time if it still branches on a secret underneath.
A certified HSM or TPM is side-channel proof, isn't it?
No. Certification validates a modeled threat surface, not physics. TPM-Fail recovered 256-bit ECDSA keys from an Intel firmware TPM and a Common Criteria EAL4+ and FIPS-certified STMicroelectronics chip via a timing-and-lattice attack -- minutes locally, hours remotely [25]. A certificate tells you the module passed a defined evaluation; it does not tell you the evaluation covered the channel your attacker will use.
My library is constant-time, so I am safe.
Necessary, not sufficient. Constant-time is a property of the instruction stream you wrote; the silicon runs one you did not. Spectre executes secret-dependent paths speculatively, Hertzbleed turns data-dependent frequency into remote timing, GoFetch's prefetcher dereferences pointer-like secrets, and KyberSlash leaks through a variable-time division -- all beneath the constant-time abstraction [5][20][1][22]. Constant-time is the floor, not the ceiling.
Power and EM attacks need physical access, so I can ignore them.
Mostly true, and you should threat-model it honestly: if no adversary can hold or instrument your device, the classical power and EM channels are lower priority than the remote ones. But the boundary is not absolute. Hertzbleed turned a power side channel into a remote timing attack by exploiting dynamic frequency scaling, extracting a key from constant-time SIKE over a network with no physical probe at all [20].
Rotation fixes a leaked key.
Rotation bounds the blast radius of a leak; it does not prevent the leak. It caps how long a compromised key stays valid and how much it protects, which is real and valuable. But it is not custody. Storm-0558's key was never rotated and software-resident; rotation would have shortened the window, while hardware custody would have stopped the export that started the whole incident [26]. Do both.
Envelope encryption is just double encryption.
No. It is custody plus performance, not redundancy. A cheap Data-Encryption Key does the bulk work while the Key-Encryption Key that wraps it never leaves the HSM or KMS, using standardized key wrapping [30]. The payoff is a bounded blast radius: a leaked DEK exposes only the one object it encrypted, and the root key stays in hardware. Double encryption gives you two layers of the same risk; envelope encryption gives you a small, cheap, disposable key over a small, protected, permanent one.
Most crypto breaks are broken ciphers.
No -- they are broken implementations and mismanaged keys. Scope the claim carefully: relative to breaking the primitives and relative to the exotic side channels this article catalogs, operational key and secret management is the dominant failure surface, evidenced by cryptographic failures ranking as the number-two web risk and by Storm-0558's uncracked-but-stolen key [39][26]. It is not a claim that key management outranks every software vulnerability class -- memory-safety bugs lead by raw count. The math almost never breaks. The handling of the math does.
The Proof Was Conditional
Twenty-four times, this series proved that the mathematics holds. Every one of those proofs carried a silent condition: supply a fresh nonce, validate the point, derive a uniform key, compare tags in constant time, keep the secret in its custody. This final part is the condition, standing alone and made visible. The math held. The machine and the operations are what handed the secret away.
You now have the lens to read any future break the same way, without waiting for someone to explain it. When the next headline attack lands, ask which of four invariants it violated. Did a secret touch time -- a branch, a division, a data-dependent frequency? Did a secret touch an address -- a table index, a cache line, a prefetched pointer? Did a fault skip a check -- a glitch, a hammered row, an undervolted enclave? Or did a key outlive or escape its custody -- hardcoded, sprawled, unrotated, software-resident? Kocher's stopwatch in 1996 and Apple's prefetcher in 2024 are the same sentence in two dialects, and Storm-0558 is the fourth invariant in its purest form: no cipher broken, a key simply gone.
So end the series where the evidence points. Spend real effort on the exotic channels, because verified constant-time and hardware timing modes are now deployable and the microarchitecture will keep testing them. But spend the larger share of your budget on the unglamorous layer, because that is where the incidents actually cluster: put the root key in hardware, use it without exporting it, envelope-wrap and auto-rotate everything else, and keep no long-lived secret sitting in software. The twenty-five parts of this guide are one argument, and this is its last line.
The math held. Make sure the machine, and the people running it, do not hand the secret away.
Study guide
Key terms
- Side channel
- Information leaked by a computation's physical or microarchitectural behavior, not its input/output relation.
- Abstraction gap
- The distance between an algorithm as an I/O relation and an implementation as a physical, timed, operated process.
- Constant-time
- Data-independent execution: instruction and address traces do not depend on secrets. Not fixed duration.
- Differential Power Analysis
- Correlating power draw across many traces to extract a key even under heavy noise.
- Fault attack
- Inducing a computational error so the faulty output reveals the secret, as in Bellcore RSA-CRT.
- Cache side channel
- Recovering which addresses a victim touched by timing shared-cache hits and misses, as in Flush and Reload.
- Transient execution
- Speculatively executed, later-discarded instructions whose cache traces still leak, as in Spectre.
- Masking
- Splitting a secret into d+1 random shares so any d observed values are independent of it.
- HSM
- A tamper-resistant boundary that uses keys without exporting them in plaintext.
- Envelope encryption
- A cheap DEK encrypts data; a KEK inside the HSM wraps the DEK; the wrapped DEK is stored beside the ciphertext.
Flashcards
Flashcards
1 / 4Comprehension questions
Why does square-and-multiply leak the exponent through timing?
A one bit triggers an extra multiply, so total time tracks the number of one bits in the secret exponent.
What made Storm-0558 a custody failure rather than a cryptographic one?
A 2016 software-resident, never-rotated signing key was stolen and used to forge tokens; no cipher was broken.
How does GoFetch break constant-time code?
Apple's data-memory-dependent prefetcher dereferences pointer-like values, coupling data to addresses below the ISA.
What does rotation fix, and what does it not fix?
It bounds a leaked key's blast radius and lifetime; it does not stop the export, which is custody's job.
References
- (2024). GoFetch: Breaking Constant-Time Cryptographic Implementations Using Data Memory-Dependent Prefetchers. https://gofetch.fail/ - Apple M-series data-memory-dependent prefetcher breaks constant-time crypto, including PQC. ↩
- (1996). Timing Attacks on Implementations of Diffie-Hellman, RSA, DSS, and Other Systems. https://paulkocher.com/doc/TimingAttacks.pdf - Origin of timing side-channel cryptanalysis; introduces RSA/DH blinding. ↩
- (1985). Electromagnetic Radiation from Video Display Units: An Eavesdropping Risk?. https://doi.org/10.1016/0167-4048(85)90046-X - First open-literature EM-emanation eavesdropping demonstration; TEMPEST prehistory. ↩
- (1999). Differential Power Analysis. https://www.rambus.com/wp-content/uploads/2015/08/DPA.pdf - Introduces Simple and Differential Power Analysis; motivates masking and hiding. ↩
- (2019). Spectre Attacks: Exploiting Speculative Execution. https://ieeexplore.ieee.org/document/8835233 - Speculative execution leaks secrets from even constant-time code via the cache. ↩
- (2003). Remote Timing Attacks are Practical. https://crypto.stanford.edu/~dabo/papers/ssl-timing.pdf - Shows RSA timing attacks are practical remotely over a network. ↩
- (2001). On the Importance of Eliminating Errors in Cryptographic Computations. https://doi.org/10.1007/s001450010016 - One faulty RSA-CRT signature factors the modulus; founds fault attacks. ↩
- (1997). Differential Fault Analysis of Secret Key Cryptosystems. https://doi.org/10.1007/BFb0052259 - Differential Fault Analysis extends fault attacks to DES and symmetric ciphers. ↩
- (2008). CVE-2008-0166: Predictable Random Number Generator in Debian OpenSSL. https://nvd.nist.gov/vuln/detail/CVE-2008-0166 - CVE-2008-0166: predictable Debian OpenSSL RNG collapsed key entropy. ↩
- (2010). Console Hacking 2010: PS3 Epic Fail (27th Chaos Communication Congress). https://media.ccc.de/v/27c3-4087-en-console_hacking_2010 - PlayStation 3 ECDSA break from a reused signing nonce (27C3). ↩
- (2017). CVE-2017-15361: Infineon RSA Library Key Generation Flaw (ROCA). https://nvd.nist.gov/vuln/detail/CVE-2017-15361 - CVE-2017-15361 (ROCA): Infineon RSA key-generation flaw. ↩
- (2005). Cache-timing attacks on AES. https://cr.yp.to/antiforgery/cachetiming-20050414.pdf - Cache-timing attack on secret-indexed AES T-tables; roots constant-time design. ↩
- (2005). Cache Missing for Fun and Profit. https://www.daemonology.net/papers/htt.pdf - First practical shared-L1-cache side channel under Hyper-Threading. ↩
- (2006). Cache Attacks and Countermeasures: The Case of AES. https://doi.org/10.1007/11605805_1 - Formalizes the Prime+Probe and Evict+Time cache-attack primitives. ↩
- (2014). FLUSH+RELOAD: A High Resolution, Low Noise, L3 Cache Side-Channel Attack. https://www.usenix.org/conference/usenixsecurity14/technical-sessions/presentation/yarom - High-resolution shared-last-level-cache Flush+Reload primitive; recovers GnuPG RSA keys. ↩
- (2013). Lucky Thirteen: Breaking the TLS and DTLS Record Protocols. https://www.isg.rhul.ac.uk/tls/Lucky13.html - Non-constant-time TLS MAC-and-padding check is a plaintext-recovering timing oracle. ↩
- (2018). Meltdown: Reading Kernel Memory from User Space. https://www.usenix.org/conference/usenixsecurity18/presentation/lipp - Transient out-of-order execution reads kernel memory via the cache. ↩
- (2018). Reading privileged memory with a side-channel. https://googleprojectzero.blogspot.com/2018/01/reading-privileged-memory-with-side.html - Original coordinated Spectre and Meltdown disclosure. ↩
- (2020). Minerva: The curse of ECDSA nonces. https://minerva.crocs.fi.muni.cz/ - ECDSA nonce bit-length timing leak plus lattice recovers keys from certified cards. ↩
- (2022). Hertzbleed: Turning Power Side-Channel Attacks Into Remote Timing Attacks on x86. https://www.hertzbleed.com/ - DVFS frequency scaling turns a power channel into remote timing; breaks constant-time SIKE. ↩
- (2023). An efficient key recovery attack on SIDH. https://eprint.iacr.org/2022/975 - Classical key-recovery attack breaks SIDH mathematically; SIKE was withdrawn. ↩
- (2024). KyberSlash: division timing side channels in Kyber/ML-KEM. https://kyberslash.cr.yp.to/ - Secret-numerator division leaks ML-KEM (Kyber) secret bits through timing. ↩
- (2014). Flipping Bits in Memory Without Accessing Them: An Experimental Study of DRAM Disturbance Errors. https://users.ece.cmu.edu/~omutlu/pub/dram-row-hammer_isca14.pdf - DRAM row hammering flips adjacent-row bits; a software-triggered hardware fault. ↩
- (2020). Plundervolt: Software-based Fault Injection Attacks against Intel SGX. https://plundervolt.com/ - Software undervolting induces faults inside Intel SGX enclaves. ↩
- (2020). TPM-FAIL: TPM meets Timing and Lattice Attacks. https://tpm.fail/ - Timing-and-lattice attack recovers ECDSA keys from certified TPMs. ↩
- (2024). Review of the Summer 2023 Microsoft Exchange Online Intrusion. http://web.archive.org/web/20260423143145/https://www.cisa.gov/sites/default/files/2025-03/CSRBReviewOfTheSummer2023MEOIntrusion508.pdf - CSRB review: an unrotated software-resident 2016 signing key forged tokens. ↩
- (2017). dude, is my code constant time?. https://github.com/oreparaz/dudect - t-test statistical constant-time leakage detection; a de-facto CI gate. ↩
- (2016). Verifying Constant-Time Implementations. https://www.usenix.org/conference/usenixsecurity16/technical-sessions/presentation/almeida - Machine verification of the constant-time property over real code. ↩
- (2006). Threshold Implementations Against Side-Channel Attacks and Glitches. https://doi.org/10.1007/11935308_38 - Threshold implementations: glitch-resistant masking against side channels. ↩
- (2012). Recommendation for Block Cipher Modes of Operation: Methods for Key Wrapping (NIST SP 800-38F). https://csrc.nist.gov/pubs/sp/800/38/f/final - Standardized authenticated key wrapping for envelope encryption. ↩
- (2020). Recommendation for Key Management, Part 1 Rev. 5 (NIST SP 800-57). https://csrc.nist.gov/pubs/sp/800/57/pt1/r5/final - Canonical key-management lifecycle and crypto-period guidance. ↩
- (2020). HACL*/EverCrypt: verified cryptographic library. https://github.com/hacl-star/hacl-star - Formally verified constant-time crypto shipping in Firefox and Linux. ↩
- (2023). A Systematic Evaluation of Automated Tools for Side-Channel Vulnerabilities Detection in Cryptographic Libraries. https://arxiv.org/abs/2310.08153 - Systematic evaluation of automated side-channel detection tooling. ↩
- (2020). Binsec/Rel: Efficient Relational Symbolic Execution for Constant-Time at Binary-Level. https://github.com/binsec/Rel - Binary-level relational verification of constant-time code and secret erasure. ↩
- (2022). Data Operand Independent Timing ISA Guidance. https://www.intel.com/content/www/us/en/developer/articles/technical/software-security-guidance/best-practices/data-operand-independent-timing-isa-guidance.html - Intel Data Operand Independent Timing mode; enumerated instructions only. ↩
- (2024). DIT, Data Independent Timing (AArch64 register). https://developer.arm.com/documentation/ddi0601/latest/AArch64-Registers/DIT--Data-Independent-Timing - Arm Data Independent Timing register; vendor-specific, partial coverage. ↩
- (2025). AWS Key Management Service Developer Guide: Concepts. https://docs.aws.amazon.com/kms/latest/developerguide/concepts.html - Envelope encryption plus automatic key rotation; Chain B state of the art. ↩
- (2019). FIPS 140-3: Security Requirements for Cryptographic Modules. https://csrc.nist.gov/pubs/fips/140-3/final - Cryptographic-module validation, including non-invasive side-channel requirements. ↩
- (2021). A02:2021 - Cryptographic Failures. https://owasp.org/Top10/2021/A02_2021-Cryptographic_Failures/ - Ranks cryptographic failures as the number-two web-application risk. ↩
- (2025). OWASP Non-Human Identities Top 10 (2025). https://owasp.org/www-project-non-human-identities-top-10/ - Non-Human Identities Top 10: secret leakage and long-lived-secret risks. ↩
- (2023). Results of Major Technical Investigations for Storm-0558 Key Acquisition. http://web.archive.org/web/20250910200924/https://msrc.microsoft.com/blog/2023/09/results-of-major-technical-investigations-for-storm-0558-key-acquisition/ - Microsoft's technical account of the Storm-0558 key acquisition. ↩
- (2024). Securing our future: September 2024 progress update on Microsoft's Secure Future Initiative. https://www.microsoft.com/en-us/security/blog/2024/09/23/securing-our-future-september-2024-progress-update-on-microsofts-secure-future-initiative-sfi/ - Storm-0558 remedy: signing keys moved to Azure Managed HSM with automatic rotation. ↩
- (2025). HSM and TPM Failures in Cloud: A Real-World Taxonomy and Emerging Defenses. https://arxiv.org/abs/2507.17655 - Taxonomy of real-world cloud HSM and TPM custody failures. ↩
- (2003). Private Circuits: Securing Hardware against Probing Attacks. https://people.eecs.berkeley.edu/~daw/papers/privcirc-crypto03.pdf - Probing model: order-d security needs d+1 shares at O(d^2) masked-multiply cost. ↩
- (2014). Unifying Leakage Models: from Probing Attacks to Noisy Leakage. https://web.archive.org/web/20210420065936/https://eprint.iacr.org/2014/079 - Noisy-leakage-to-probing reduction; masking security amplifies exponentially in shares. ↩
- (2018). Speculative Load Hardening. https://llvm.org/docs/SpeculativeLoadHardening.html - LLVM Speculative Load Hardening; a Spectre v1 mitigation with measured overhead. ↩
- (2024). Towards Efficient Verification of Constant-Time Cryptographic Implementations. https://arxiv.org/abs/2402.13506 - Scalable constant-time verification frontier (CT-Prover). ↩
- (2013). RFC 6979: Deterministic Usage of the Digital Signature Algorithm (DSA) and Elliptic Curve Digital Signature Algorithm (ECDSA). https://datatracker.ietf.org/doc/html/rfc6979 - Deterministic ECDSA/DSA nonce generation without high-quality randomness. ↩
- (2018). Attacking Deterministic Signature Schemes using Fault Attacks. https://doi.org/10.1109/EuroSP.2018.00031 - Fault attacks on deterministic signature schemes recover the key. ↩