Breaking Optimized HQC: The First Cache-Timing Full Decryption Oracle Key-Recovery... (Crypto 2026)
Watch on YouTubeVideo summary
The video presents a significant cryptographic breakthrough regarding optimized HQC, a code-based encryption scheme selected by NIST as an alternative to ML-KEM and currently moving toward standardization under FIPS 207. While previous research has focused on physical side-channel attacks requiring privileged access or proximity, this study investigates the resilience of high-performance AVX2 implementations against unprivileged adversaries operating in user space on commodity CPUs. The researchers demonstrate that compiler optimizations intended to improve performance can inadvertently undermine source-level constant-time guarantees. Specifically, they identify a vulnerability where certain versions of the Clang compiler transform logic designed for mask selection into secret-dependent conditional branches within the binary code during the Reed-Muller decoding process. This transformation creates cache timing leakage channels that allow an attacker to distinguish between different decoding results by monitoring whether specific cache lines are accessed or missed using flush-and-reload techniques.
To exploit this vulnerability, the attackers construct a full decryption oracle without needing physical access to the victim's machine. By carefully crafting ciphertexts with chosen error patterns and randomness values, they force the decoder into states that trigger these secret-dependent branches only for specific subsets of decoding results. When an attacker flushes a targeted cache line and measures the time required to reload it, a fast load indicates a "hit," meaning the victim accessed that memory location during decryption, while a slow load indicates a "miss." Through calibration and statistical analysis using offline templates, these hit-or-miss observations are converted into soft information regarding individual bits of the secret key. The study validates this approach on Apple Silicon hardware, showing that even with system jitter and prefetching challenges, specific techniques can reliably isolate target blocks to extract meaningful data about the private keys over multiple queries.
The final phase of the attack involves recovering the full sparse secret vector using a newly developed GPU-accelerated soft information set decoding implementation. This tool leverages the probabilistic reliability scores derived from the side-channel oracle to prioritize which key bits are likely zero versus one, significantly increasing the efficiency of the search process compared to traditional methods. On high-end hardware like the Mac Studio M3 Ultra, parallelized workers can perform multiple attempts per second, successfully recovering keys within a practical timeframe involving less than ten seconds of online data collection followed by under thirty minutes of post-processing. The results confirm that even with static linking or different compiler versions, similar vulnerabilities often persist unless explicitly verified against the emitted binary rather than just trusting source code annotations, highlighting a critical gap between theoretical security claims and actual implementation behavior in optimized cryptographic libraries.
Read the full video transcript
Hi everyone. How are you doing? I'll
present joint work with Chen Gu on a new
cache timing for decryption key recovery
attack on optimized HQC.
HQC has been selected by NIST as a
code-based alternative to ML-KEM and is
moving towards standardization as FIPS
207.
Before its deployment, understanding its
implementation security is very
important.
We already know from previous work on
ML-KEM that compiler optimizations can
undermine source-level constant-time
guarantees.
For example, Kyber/Slash exposed the
variable-time division instructions and
other cases involved the
compiler-generated secret-dependent
control flows.
For HQC, much of the recent work has
focused on reference implementations
using power or electromagnetic
measurements that typically require
physical access or close proximity or
controlled channel attackers with
privileged capabilities.
This leaves an important question. How
resilient is the high-performance AVX2
optimized implementation against the
standard unprivileged adversary?
Our focus is the optimized HQC
implementation running on a commodity
CPU with a co-located unprivileged user
space attacker.
Under this threat model, we demonstrate
an end-to-end key recovery attack on the
AVX2 optimized implementation.
This work has three main contributions.
First, we identify a compiler-induced
vulnerability in a find peaks function
of the inner Reed-Muller decoder. The
source code uses constant time mask
selection. However, certain versions of
the clan compiler transform this logic
into secret dependent
conditional branches in the compiled
binary.
Second, we exploit the resulting control
flow using flush and reload on the
shared HQC library to build an full
decryption style oracle. With carefully
chosen ciphertext, the cache trace
reveals one bit information of the
decoded byte for individual Reed-Muller
blocks.
Lastly, we develop a GPU accelerated
soft information set decoding
implementation.
Using oracle predictions from the side
channel and offline templates, we obtain
reliability information about the secret
key bits.
The soft IAS implementation then uses
this information to recover the secret
key vector.
For HQC 1, the online trace collection
takes less than 10 seconds followed by
less than 30 minutes of post-processing.
Here is the overview of the
presentation. I will start with the
relevant HQC background.
The HQC PKE core has three components:
key generation, encryption, and
decryption.
During key generation, two sparse secret
vectors X and Y are sampled together
with a uniformly random vector H.
The public key consists of H and S, and
S is computed from X, Y, and H.
To encrypt a message, the scheme derives
three sparse vectors R1, R2, and E. It
then computes ciphertext components U
and V using these values, the message M,
and the public key components.
During decryption, the receiver first
uses the ciphertext and his secret key
to compute the received vector V.
The decoder now attempts to recover a
message M prime from this received
vector.
The CAM wraps this PKE using an F4
transform. The encapsulation first
decrypts the ciphertext, derives new
encryption randomness from the recovered
message, and re-encrypts to validate the
ciphertext.
More details of the decryption process
that our attack targets.
HQC uses a concatenated code consisting
of an outer Reed-Solomon code and an
inner duplicated Reed-Muller code.
Received vector V is divided into N1
independent blocks.
Each block is processed by Reed-Muller
decoder.
The block decodings are independent, so
they can be parallelized.
However, in software, they're typically
processed uh sequentially in a loop.
Three main operations are performed.
First, expand and assume combines the
duplicated components.
Next, the Hadamard transform computes
the relevant correlations. Finally, find
peaks identifies the largest correlation
output of the Reed-Muller decoding byte
for that block, denoted as MI hat.
The collection of these bytes is then
passed to the outer Reed-Solomon decoder
to produce the final message M prime.
Full decryption oracle attack on HQC has
been proposed in previous work. The
attack exploits the decoding of the N1
Reed-Muller blocks. Our work builds on
this FD attack.
A full decryption oracle is defined as
one that returns the N1 Reed-Muller
decoding results in a single query
with perfect accuracy. Each result is
exactly the decoded byte for that
The attacker constructs chosen
ciphertext to probe the secret key part
X or Y separately.
For example, by setting R1 to 0 and R2
to 1, the received vector in the
decryption process consists of the
secret part X, the encoded message, and
a chosen error vector E.
The error vector E is constructed by
repeating the same optimized error
pattern epsilon across all N1 blocks.
The offline template method is used to
obtain soft information about secret key
bits.
Given a message M and the carefully
chosen error pattern epsilon, one can
build offline templates that maps each
attainable decoding result to the
empirical probability that each position
in a secret key block is one.
In this way, side channel leakage on the
Reed-Muller decoding result can be
turned into soft information about a
secret key bits.
Our attack is based on this FD attack
framework.
In our setting, Alice is the CCA
attacker and Bob is the victim. Alice is
collocated with Bob and operates
entirely in user space without
administrative privileges.
Their processes share the last level
cache, and the HQ implementation is
loaded as a shared library.
At the core of the attack is a
compiler-induced conditional branching
during Reed-Muller coding. As a result,
a particular cache line is accessed only
for certain Reed-Muller decoding
results. This creates a cache timing
leakage channel that Alice can exploit
using flush and reload.
To launch the attack, Alice constructs
malformed ciphertext using Bob's public
key and specific values of R1, R2, and
E, and then submits them to Bob for
decapsulation.
When Bob is decapsulating, Alice
monitors the vulnerable cache line. She
flashes the line from the cache with
briefly and then reloads it while
measuring the reload time.
A faster reload tells Alice that Bob
accessed the line during decapsulation.
While a slow reload indicates that Bob
did not access the line.
Each cache hit or miss derive or reviews
the category of the Reed-Muller decoding
result.
Pre-built offline templates translate
these observations into soft information
about the secret key bits. Finally, our
new soft S-D implementation combines
this information with the public key to
recover the full secret key.
We now look at how the vulnerability is
introduced. On the left is the relevant
source code from the find pics function.
The loop selects one of the 16 values
according to the lower four bits of the
Reed-Muller decoding result.
The selection uses a mask together with
bitwise operations.
All 16 iterations are intended to follow
the same control flow.
On the right is the binary generated for
the AVX2 implementation by clang 14 with
optimization level O3.
Here, the lower four bits of the
Reed-Muller decoding result are stored
in the register RDI.
Instead of preserving the mask
selection, the compiler transforms it
into sequence of conditional jumps.
It compares RDI with values from 0 to 15
and branches to different code blocks
depending on whether the comparison
evaluates to true or false.
The figure on the left illustrates this
conditional branching in a binary. RDI
is first tested against 0 and execution
branches to one of two basic blocks
depending on whether RDI is 0.
In both of these blocks, RDI is then
compared with one. If RDI equals to one,
execution moves to the same compare true
block in a blue region, regardless of uh
which preceding block it came from.
Otherwise, execution moves to the same
compare false block in a red region.
In both destination blocks, RDI is next
compared with two, and this pattern of
comparison and branching continues. Take
the first block in a blue region as an
example. It is executed only when RDI
equals one. Its counterpart in a red
region is executed otherwise. The
execution of each basic block therefore
identifies a subset of Reed-Muller
decoding results.
For nonzero RDI, exactly one blue block
is executed. In our build, the cache
line B600 contains the blue blocks for
RDI values 3, 4, 5, and 6. So, it is
accessed exactly when M hat modulo 16
lies in this set.
By monitoring whether this cache line is
accessed, we construct a hit-miss oracle
that tells whether an Reed-Muller
decoding result belongs to this
category.
Knowing that a cache line hit or miss
reveals the category of an Reed-Muller
decoding result, we want to see whether
this category is related to the
reliabilities of the secret key bits as
in the offline template method.
We optimize the error pattern epsilon so
that when it is combined with the random
secret key block, the decoding result
falls into the hit category with
approximately 50% probability.
We then build an offline template for
this error pattern and plot the
empirical probability that a bit at each
position in a secret key block is one
conditional on a hit or miss.
The figure clearly shows that these
probabilities deviate from the a priori
probability.
This confirms that a hit-or-miss leaks
information about secret key bits.
To realize this hit-miss algorithm
practice, we first calibrate the cache
timing channel.
To distinguish a hit from a miss, we
measure load times from the L1 cache and
main memory
and select a threshold of 110 clock
cycles.
In a flush and reload traces, a reload
time below this value is classified as a
hit, while one above this value is a
miss.
To determine the temporal resolution of
the channel, we measure the number of
clock cycles per Reed-Muller block
decoding.
We find per block decoding short
relative to the interval required for
reliable flush and reload probe.
System jitter and prefetching also make
it difficult to associate each
hit-or-miss with the corresponding
iteration of the Reed-Muller decoding
loop.
We therefore design a several texts to
space out the target blocks, so that
each several text targets only one or a
few blocks rather than all and one
blocks at once.
Here are the two several text
constructions we use.
R1 and R2 are chosen as in the standard
FD attack.
The difference lies in how the error
vector is configured. In method one to
target block B of the secret key vector,
we place the optimized error pattern
epsilon in the corresponding block of
the error vector and set all other
blocks to zero.
In this way, the observed the
hit-or-miss is determined entirely by
the Reed-Muller decoding result of the
target block.
In method two, an always hit pattern is
put in block zero as a synchronization
marker, followed by six zero blocks.
From block seven onward, epsilon is
placed in three blocks spaced 13 blocks
apart.
In this way, with each ciphertext, we
target three blocks in a key vector, but
we lose information about the first
seven blocks.
We validate our tag with 30 random HQC-1
key pairs using a MacBook Pro with an
Intel i7 processor and the Musique
toolkit for flush and reload.
For method one, each ciphertext is
repeated 50 times. We classify the
target block as a hit
only if the hit count exceeds 40.
For method two, each ciphertext is
repeated 11 times. The always hit block
block zero is used to align the traces.
We divide the aligned trace into
consecutive regions. Each region is
assigned to one of the three target
blocks.
We then determine which blocks' oracle
output by majority voting over the 11
traces.
The oracle predictions are mapped into
key bit reliabilities using the offline
templates and then aggregated across
towel error patterns.
We rank the secret key positions from
most likely to be zero to most likely to
be one. As more error patterns are used,
the average number of actual ones in the
first M positions decreases.
This shows that the reliability ranking
improves while more
error patterns are used and this
provides useful input to
uh the soft um
information set decoding.
The remaining task is recover the sparse
secret vector from the public syndrome
equation.
An SD attempt is more likely to succeed
when the information set contains only
the few non-zero secret bits.
From the side channel, we gain soft
information LJ for each secret key
position J.
We use this information to favor
positions that are more likely to be
zero when selecting the information set.
We developed a new soft ISD
implementation for Apple Silicon
building on the weighted sampling
approach of prior work. Each attempt
uses a two-list meet-in-the-middle
search with multiple re-randomized runs.
The Apple GPU accelerates the parallel
hash join step.
On a Mac Studio M3 Ultra, 28 parallel
workers together achieve around 2.3 to
2.5 attempts per second.
Each worker uses roughly 7 GB of memory
for about 200 GB in total.
This figure summarizes the full key
recovery results across 30 randomly
generated keys.
Each run is capped at 2 to the power of
12 attempts. The percentages show the
success rate for each configuration, and
the box plots show the number of
attempts for successful recoveries.
To conclude, certain clang versions
transform the masked selection in find
peaks into secret-dependent branches in
a emitted binary for the optimized HQC
implementation.
The resulting cache line access leaks
information about the inner Reed-Muller
decoding output.
We show that this enables an FD-style
oracle attack using unprivileged flash
and reload against a shared HQC library.
We develop a GPU-accelerated
soft ISD implementation to turn this
leakage into full key recovery.
We also find that similar
compiler-induced vulnerabilities appear
in other clang versions.
Static linking removes the shared
library condition for this particular
attack, but not the underlying leakage,
which may still be exploited using prime
and probe or interested execution
environments.
So, constant time claims must always be
verified on the emitted binary.
This concludes my presentation. Thank
you for your attention.