Skip to content
KitploitKITPLOIT
ToolsBlog
Submit
ToolsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

··Feeds·Contact·Privacy·© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
ml-kem-key-recovery — Full ML-KEM-1024 key recovery from a partial Fujisaki-Okamoto comparison in wolfSSL (CVE-2026-6330 NEON, CVE-2026-10097 AVX2) | Kitploit
Tools/GitHubGitHub/007bsd/ml-kem-key-recovery
Vulnerability AnalysisExploitationPost-ExploitationCryptographyBinary AnalysisPapers & Research
GitHub007bsd/ml-kem-key-recovery

ml-kem-key-recovery

Full ML-KEM-1024 key recovery from a partial Fujisaki-Okamoto comparison in wolfSSL (CVE-2026-6330 NEON, CVE-2026-10097 AVX2)

View Repository
125 days agoNot yet reviewed

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share

Full key recovery from a partial Fujisaki-Okamoto comparison in wolfSSL ML-KEM

CVE-2026-6330 (NEON) and CVE-2026-10097 (AVX2). Fixed in wolfSSL 5.9.2.

The full technical write-up is published as Cryptology ePrint Archive, Paper 2026/1682 (local copy in paper/). See Citation below.

Introduction

ML-KEM (Kyber, FIPS 203) resists chosen-ciphertext attacks because of one check inside decapsulation: the receiver re-encrypts the message it just decrypted and returns the real shared secret only if the result matches the input ciphertext exactly. Any mismatch triggers implicit rejection, and a pseudorandom value is returned instead. That check is the Fujisaki-Okamoto transform, the single gate separating the IND-CCA2 scheme from the malleable IND-CPA scheme underneath.

wolfSSL implemented that comparison in hand-written assembly, and on two backends it compared only part of the ciphertext. On ARM64 it compared about half, which () found and reported (). On x86-64 it compared 1536 of the 1568 bytes in ML-KEM-1024 ().

SIMD
NEON
Nicholas Carlini
Wikipedia
CVE-2026-6330
AVX2
CVE-2026-10097

On either backend a tampered ciphertext is accepted as genuine, which was reported as a weakening of IND-CCA2 security. This write-up shows the flaw goes further, on both backends. The bytes the check skips leak the decapsulation's decryption noise, and that noise is a linear function of the secret key, so the key can be recovered by least-squares regression. It is not a lattice problem, and it is not the classical key-mismatch attack: on the AVX2 backend the flaw validates all of u, so the chosen-u ciphertexts those attacks require are rejected. The decryption-noise regression instead recovers the key from the unchecked v-coefficients. The full ML-KEM-1024 private key is recovered end-to-end against the live vulnerable binary on both the AVX2 and NEON backends.

The attack needs a reused ML-KEM key: HPKE recipients, KEMTLS, or pinned and embedded keys. An ephemeral hybrid TLS 1.3 key share uses a fresh key per handshake and is not key-recoverable this way; there the flaw is only a distinguishing break. Both flaws are fixed and public, and this is a post-disclosure write-up.

The two instances

NEON (CVE-2026-6330)AVX2 (CVE-2026-10097)
BackendARM64 NEONx86-64 AVX2
Flawcompares ~half the ciphertextcompares 1536 of 1568 bytes
Bug reported byNicholas Carlini (Anthropic)007bsd
CVE severityMedium (CVSS 4.0 6.3, CWE-327)High (CVSS 4.0 8.3, CWE-697)
Key recovery, modelfull (~500 ct)full (~1300 ct)
Key recovery, live binary98.5% @ 600 ct (QEMU-emulated)98.1% @ 350 ct (native)
FixPR #10192PR #10430

"live" is the demonstrated result on the real binary: NEON needs more ciphertexts (600 vs 350) because its per-coefficient measurement is far noisier (about 4x). "model" is a noise-free check that the regression recovers the full key (all 2048 coefficients, 100%); its ciphertext count is set by how many equations each ciphertext yields, not by noise, so it is not comparable to the live figure and does not reflect attack difficulty.

Both flaws were found independently in the same wolfSSL release window and fixed together in 5.9.2 (see the wolfSSL security vulnerabilities page for both CVE entries). Carlini's NEON bug was documented as an IND-CCA2 weakening, and the AVX2 bug is CVE'd as key recovery. The decryption-noise regression attack in this repository recovers the key on both. Backend-specific detail is in neon-cve-2026-6330/ and avx2-cve-2026-10097/.

FAQ

What is the vulnerability?

An incomplete comparison in wolfSSL's ML-KEM implicit-rejection check, present in two SIMD assembly backends. It compared fewer than all of the ciphertext bytes, so decapsulation accepts ciphertexts that a correct implementation rejects. The unchecked bytes turn decapsulation into an oracle for the decryption noise, and that is enough to recover the private key when the key is reused.

Am I affected?

You are affected if you used wolfSSL with ML-KEM on a build with the vulnerable SIMD assembly: AVX2 (x86-64) in 5.7.0-5.9.1, or ARM64 NEON in 5.7.4-5.9.1. The key-recovery attack additionally needs the ML-KEM private key to be reused across decapsulations, as with HPKE recipients, KEMTLS, or a pinned or embedded key. An ephemeral hybrid TLS 1.3 key share uses a fresh key per handshake and is not key-recoverable; there the flaw is only a distinguishing break. The fix is in wolfSSL 5.9.2.

What can an attacker do?

Recover the entire ML-KEM-1024 secret key on either backend, by sending crafted decapsulation queries and observing, for each, whether the real secret or a reject value comes back. Against the live binaries this recovered the key with roughly 10^5 to 10^6 decapsulation queries.

How does the attack work?

Take one ciphertext coefficient in the unchecked region. Its plaintext bit is decided by rounding, m'_j = Compress_1(v_j - (s^T u)_j). Because those bytes are not compared, sweeping the compressed value of v_j and watching the one decapsulation output that changes locates a rounding boundary, and the boundary position measures the decryption noise δ_j to a precision of about ±q/64. In terms of the secret (s, e) and the encryption randomness the attacker chose:

root@kitploit:~
δ_j = ( e^T y - s^T(e1 + c_u) + e2 + c_v )_j        (verified exact; small; never wraps mod q)

That is a linear equation in the 2048-coefficient secret (s, e), with coefficients the attacker knows. Each ciphertext gives one such equation per unchecked coefficient. Stacking enough ciphertexts and solving the normal equations recovers the whole key, and the last few coefficients are pinned by the public-key relation e = t - A·s ∈ CBD(η). It is ordinary least squares, not a lattice problem.

The two backends differ in how much they leak. NEON leaves about 50% of the ciphertext unchecked (125 coefficients in three bands), against about 2% for AVX2 (51 coefficients in one band). NEON still needs more ciphertexts, because its unchecked bytes are non-contiguous and its per-coefficient measurement is noisier, so leaking more does not make the recovery easier here.

Why don't the usual ML-KEM key-recovery attacks apply?

Plaintext-checking and key-mismatch attacks (Ravi et al. 2020, Qin et al. 2021, Băetu et al. 2019) recover the key in a few thousand queries by submitting ciphertexts with attacker-chosen sparse u to isolate one secret coefficient per query. The AVX2 flaw rejects all of them: it skips only 32 bytes of v and validates the whole of u, so on the live binary a sparse-u ciphertext is rejected and flipping a single u bit is rejected 30 times out of 30. The NEON flaw is broader, leaving about half of u unchecked as well, so that argument is AVX2-specific; but the attack does not rely on it. The decryption-noise regression recovers the key from the unchecked v-coefficients on both backends, surviving the part of the check each bug leaves intact.

The relevant precedent, then, is not those key-mismatch attacks but decryption-failure attacks (D'Anvers et al. 2019, ref 6). They exploit the same decryption-noise term, a linear function of the secret, but only through the rare event of that term crossing the decoding boundary and causing a decryption failure, so recovery there is statistical and needs vastly more ciphertexts. The incomplete comparison instead makes that same term directly measurable, to about ±q/64, so no failures are needed and the attack collapses into the ordinary least-squares regression above.

How was it demonstrated?

In a faithful reference model (kyber-py) for both backends, the identity above is verified exact and regression recovers all 2048 secret coefficients. Against the live pre-fix wolfSSL binaries, attacking a reused exported key, each harness runs a self-check against the oracle's exported ground-truth key before it reports any number:

  • AVX2 (native x86-64): 65.5% (100 ct), 87.8% (200 ct), 96.9% (350 ct), reaching 98.0% of the full secret (s, e) at 400 ciphertexts. The secret s alone is 1005/1024 = 98.1% at 350 ct, the figure recorded in the CVE.
  • NEON (ARM64 under QEMU emulation): 45.7% (50 ct), 85.2% (200 ct), reaching 2018/2048 = 98.5% of the full secret (s, e) at 600 ciphertexts, with the solver error converging monotonically toward exact.

The recovered key is the wolfSSL-generated keypair the oracle exports, and the attack runs against the shipped-form SIMD assembly rather than a model of it. Full transcripts: avx2-cve-2026-10097/live_recover_avx2.out, neon-cve-2026-6330/live_recover_neon.out.

How do I fix it?

Upgrade to wolfSSL 5.9.2 or later (PR #10430 for AVX2, PR #10192 for NEON).

How serious is it?

High, though not catastrophic. It needs a reused key, an accept/reject oracle, and a large but practical number of queries. It is not a side channel: there is no timing or power measurement, only a logic bug in a comparison. It does not affect correct implementations or the ML-KEM standard itself. For comparison, a compiler-induced timing leak in liboqs's ML-KEM decapsulation (CVE-2024-36405), a related full-secret-key-recovery issue from the 2024 wave of ML-KEM timing leaks alongside KyberSlash, was rated CVSS 7.5 by NIST.

Reproduction

Per-backend attack code, analysis, and reproduction steps are in avx2-cve-2026-10097/ and neon-cve-2026-6330/.

Credits

This repository documents two sibling findings from the same wolfSSL release window, and one attack that breaks both.

  • The NEON flaw, CVE-2026-6330, was found and reported by Nicholas Carlini (website, Wikipedia), a researcher at Anthropic, and documented as an IND-CCA2 weakening.
  • The AVX2 flaw, CVE-2026-10097, and the decryption-noise key-recovery attack demonstrated on both backends, are by 007bsd.

References

  1. wolfSSL. CVE-2026-6330 (NEON, N. Carlini) · NVD · PR #10192.
  2. wolfSSL. CVE-2026-10097 (AVX2) · NVD · PR #10430.
  3. P. Ravi, S. S. Roy, A. Chattopadhyay, S. Bhasin. Generic Side-channel Attacks on CCA-secure lattice-based PKE and KEMs. TCHES 2020(3). ePrint 2019/948.
  4. Y. Qin, C. Cheng, X. Zhang, Y. Pan, L. Hu, J. Ding. A Systematic Approach and Analysis of Key Mismatch Attacks on Lattice-Based NIST Candidate KEMs. ASIACRYPT 2021. ePrint 2021/123.
  5. C. Băetu, F. B. Durak, L. Huguenin-Dumittan, A. Talayhan, S. Vaudenay. Misuse Attacks on Post-Quantum Cryptosystems. EUROCRYPT 2019. ePrint 2019/525.
  6. J.-P. D'Anvers, Q. Guo, T. Johansson, A. Nilsson, F. Vercauteren, I. Verbauwhede. Decryption Failure Attacks on IND-CCA Secure Lattice-Based Schemes. PKC 2019. IACR PDF.
  7. NIST. FIPS 203: Module-Lattice-Based Key-Encapsulation Mechanism Standard.
  8. liboqs ML-KEM compiler-induced timing leak. CVE-2024-36405 (NIST CVSS 7.5). Distinct from but concurrent with KyberSlash (secret-dependent division timing), D. J. Bernstein et al., ePrint 2024/1049.
  9. G. Pope. kyber-py: a pure-Python implementation of ML-KEM (FIPS 203). GitHub (MIT OR Apache-2.0). Used here as the reference model for the decryption-noise identity and recovery simulation.

Disclosure

Both issues were reported privately, fixed, and assigned CVEs before this write-up. Fixed in wolfSSL 5.9.2.

Citation

The full write-up is published as Cryptology ePrint Archive, Paper 2026/1682 (local copy: paper/).

root@kitploit:~
@misc{cryptoeprint:2026/1682,
      author = {Bhabani Sankar Das},
      title = {Incomplete Ciphertext Comparison in {ML}-{KEM}: From an {IND}-{CCA2} Break to Key Recovery},
      howpublished = {Cryptology {ePrint} Archive, Paper 2026/1682},
      year = {2026},
      url = {https://eprint.iacr.org/2026/1682}
}
Download Tool