
Recuperación completa de la clave ML-KEM-1024 a partir de una comparación parcial de Fujisaki-Okamoto en wolfSSL (CVE-2026-6330 NEON, CVE-2026-10097 AVX2)
CVE-2026-6330 (NEON) y CVE-2026-10097 (AVX2). Corregido en wolfSSL 5.9.2.
El informe técnico completo se publica como
Cryptology ePrint Archive, Paper 2026/1682
(copia local en paper/). Véase Citación más abajo.
ML-KEM (Kyber, FIPS 203) resiste ataques de texto cifrado elegido gracias a una comprobación dentro de la desencapsulación: el receptor re-cifra el mensaje que acaba de descifrar y devuelve el secreto compartido real solo si el resultado coincide con el texto cifrado de entrada exactamente. Cualquier discrepancia activa el rechazo implícito y se devuelve un valor pseudoaleatorio en su lugar. Esa comprobación es la transformada de Fujisaki-Okamoto, la única puerta que separa el esquema IND-CCA2 del esquema maleable subyacente.
wolfSSL implementó esa comparación en ensamblador SIMD escrito a mano, y en dos backends comparó solo parte del texto cifrado. En ARM64 NEON comparó aproximadamente la mitad, lo cual Nicholas Carlini (Wikipedia) encontró y reportó (CVE-2026-6330). En x86-64 AVX2 comparó 1536 de los 1568 bytes en ML-KEM-1024 (CVE-2026-10097).
En cualquiera de los dos backends, un texto cifrado manipulado se acepta como genuino, lo cual se reportó como un
debilitamiento de la seguridad IND-CCA2. Este informe muestra que la falla va más allá, en ambos
backends. Los bytes que la comprobación omite filtran el ruido de descifrado de la desencapsulación, y ese
ruido es una función lineal de la clave secreta, por lo que la clave puede recuperarse mediante
regresión por mínimos cuadrados. No es un
problema de retículos, y no es el ataque clásico de discrepancia de claves: en el backend AVX2 la
falla valida todo u, por lo que los textos cifrados con u elegido que requieren esos ataques son rechazados.
La regresión del ruido de descifrado, en cambio, recupera la clave a partir de los coeficientes v no comprobados.
La clave privada completa de ML-KEM-1024 se recupera de extremo a extremo contra el binario vulnerable en vivo
en ambos backends, AVX2 y NEON.
El ataque requiere una clave ML-KEM reutilizada: destinatarios HPKE, KEMTLS o claves fijadas e incrustadas. Un intercambio de claves híbrido efímero TLS 1.3 usa una clave nueva por handshake y no es recuperable de esta manera; allí la falla es solo una ruptura de distinción. Ambas fallas están corregidas y son públicas, y este es un informe posterior a la divulgación.
| NEON (CVE-2026-6330) | AVX2 (CVE-2026-10097) | |
|---|---|---|
| Backend | ARM64 NEON | x86-64 AVX2 |
| Falla | compara ~la mitad del texto cifrado | compara 1536 de 1568 bytes |
| Error reportado por | Nicholas Carlini (Anthropic) | 007bsd |
| Gravedad CVE | Media (CVSS 4.0 6.3, CWE-327) | Alta (CVSS 4.0 8.3, CWE-697) |
| Recuperación de clave, modelo | completa (~500 ct) | completa (~1300 ct) |
| Recuperación de clave, binario en vivo | 98.5% @ 600 ct (emulado con QEMU) | 98.1% @ 350 ct (nativo) |
| Corrección | PR #10192 | PR #10430 |
"en vivo" es el resultado demostrado en el binario real: NEON necesita más textos cifrados (600 vs 350) porque su medición por coeficiente es mucho más ruidosa (aproximadamente 4x). "modelo" es una comprobación sin ruido de que la regresión recupera la clave completa (los 2048 coeficientes, 100%); su cantidad de textos cifrados la determina cuántas ecuaciones produce cada texto cifrado, no el ruido, por lo que no es comparable con la cifra en vivo y no refleja la dificultad del ataque.
Ambas fallas se encontraron de forma independiente en la misma ventana de lanzamiento de wolfSSL y se corrigieron juntas
en 5.9.2 (véase la
página de vulnerabilidades de seguridad de wolfSSL para
ambas entradas CVE). El error NEON de Carlini se documentó como un debilitamiento de IND-CCA2, y el error AVX2 está
registrado como CVE de recuperación de clave. El ataque de regresión del ruido de descifrado en este repositorio recupera
la clave en ambos. El detalle específico por backend está en
neon-cve-2026-6330/ y
avx2-cve-2026-10097/.
Una comparación incompleta en la comprobación de rechazo implícito de ML-KEM de wolfSSL, presente en dos backends de ensamblador SIMD. Comparó menos de todos los bytes del texto cifrado, por lo que la desencapsulación acepta textos cifrados que una implementación correcta rechaza. Los bytes no comprobados convierten la desencapsulación en un oráculo para el ruido de descifrado, y eso es suficiente para recuperar la clave privada cuando la clave se reutiliza.
Está afectado si usó wolfSSL con ML-KEM en una compilación con el ensamblador SIMD vulnerable: AVX2 (x86-64) en 5.7.0-5.9.1, o ARM64 NEON en 5.7.4-5.9.1. El ataque de recuperación de clave además necesita que la clave privada de ML-KEM se reutilice entre desencapsulaciones, como con destinatarios HPKE, KEMTLS, o una clave fijada o incrustada. Un intercambio de claves híbrido efímero TLS 1.3 usa una clave nueva por handshake y no es recuperable; allí la falla es solo una ruptura de distinción. La corrección está en wolfSSL 5.9.2.
Recuperar la clave secreta completa de ML-KEM-1024 en cualquiera de los dos backends, enviando consultas de desencapsulación manipuladas y observando, para cada una, si vuelve el secreto real o un valor de rechazo. Contra los binarios en vivo, esto recuperó la clave con aproximadamente 10^5 a 10^6 consultas de desencapsulación.
Tome un coeficiente de texto cifrado en la región no comprobada. Su bit de texto plano se decide por
redondeo, m'_j = Compress_1(v_j - (s^T u)_j). Debido a que esos bytes no se comparan,
barrer el valor comprimido de v_j y observar la única salida de desencapsulación que
cambia localiza un límite de redondeo, y la posición del límite mide el ruido de descifrado
δ_j con una precisión de aproximadamente ±q/64. En términos del secreto (s, e) y la
aleatoriedad de cifrado que el atacante eligió:
δ_j = ( e^T y - s^T(e1 + c_u) + e2 + c_v )_j (verificado exacto; pequeño; nunca envuelve mod q)
Esa es una ecuación lineal en el secreto de 2048 coeficientes (s, e), con coeficientes que el
atacante conoce. Cada texto cifrado da una ecuación de este tipo por coeficiente no comprobado. Apilando
suficientes textos cifrados y resolviendo las ecuaciones normales se recupera toda la clave, y los últimos
pocos coeficientes se fijan mediante la relación de clave pública e = t - A·s ∈ CBD(η). Es
mínimos cuadrados ordinarios, no un
problema de retículos.
Los dos backends difieren en cuánto filtran. NEON deja aproximadamente el 50% del texto cifrado sin comprobar (125 coeficientes en tres bandas), frente a aproximadamente el 2% para AVX2 (51 coeficientes en una banda). NEON aún necesita más textos cifrados, porque sus bytes no comprobados no son contiguos y su medición por coeficiente es más ruidosa, por lo que filtrar más no hace que la recuperación sea más fácil aquí.
Los ataques de comprobación de texto plano y de discrepancia de claves (Ravi et al. 2020, Qin et al. 2021, Băetu et
al. 2019) recuperan la clave en unos pocos miles de consultas enviando textos cifrados con
u disperso elegido por el atacante para aislar un coeficiente secreto por consulta. La falla AVX2
los rechaza todos: omite solo 32 bytes de v y valida todo u, por lo que
en el binario en vivo un texto cifrado con u disperso se rechaza y voltear un solo bit de u se
rechaza 30 veces de 30. La falla NEON es más amplia, dejando también aproximadamente la mitad de u sin comprobar,
por lo que ese argumento es específico de AVX2; pero el ataque no depende de ello. La
regresión del ruido de descifrado recupera la clave a partir de los coeficientes v no comprobados en ambos
backends, sobreviviendo a la parte de la comprobación que cada error deja intacta.
El precedente relevante, entonces, no son esos ataques de discrepancia de claves sino los ataques de fallo de descifrado (D'Anvers et al. 2019, ref 6). Explotan el mismo término de ruido de descifrado, una función lineal del secreto, pero solo a través del evento raro de que ese término cruce el límite de decodificación y cause un fallo de descifrado, por lo que la recuperación allí es estadística y necesita muchísimos más textos cifrados. La comparación incompleta, en cambio, hace que ese mismo término sea directamente medible, con aproximadamente ±q/64, por lo que no se necesitan fallos y el ataque se reduce a la regresión de mínimos cuadrados ordinarios anterior.
En un modelo de referencia fiel (kyber-py) para ambos backends, la identidad anterior se verifica exacta y la regresión recupera los 2048 coeficientes secretos. Contra los binarios wolfSSL en vivo anteriores a la corrección, atacando una clave exportada reutilizada, cada harness ejecuta una autocomprobación contra la clave de verdad fundamental exportada del oráculo antes de reportar cualquier número:
(s, e) con 400 textos cifrados. Solo el secreto s es 1005/1024 = 98.1% con
350 ct, la cifra registrada en el CVE.(s, e) con 600 textos cifrados, con el error del solucionador convergiendo
monótonamente hacia lo exacto.La clave recuperada es el par de claves generado por wolfSSL que el oráculo exporta, y el ataque se ejecuta
contra el ensamblador SIMD en su forma distribuida, no contra un modelo del mismo. Transcripciones completas:
avx2-cve-2026-10097/live_recover_avx2.out,
neon-cve-2026-6330/live_recover_neon.out.
Alta, aunque no catastrófica. Requiere una clave reutilizada, un oráculo de aceptación/rechazo y una cantidad grande pero práctica de consultas. No es un canal lateral: no hay medición de tiempo ni de consumo, solo un error lógico en una comparación. No afecta a implementaciones correctas ni al propio estándar ML-KEM. Para comparar, una fuga de tiempo inducida por el compilador en la desencapsulación de ML-KEM de liboqs (CVE-2024-36405), un problema relacionado de recuperación completa de clave secreta de la ola de fugas de tiempo de ML-KEM de 2024 junto con KyberSlash, fue calificado con CVSS 7.5 por NIST.
El código de ataque por backend, el análisis y los pasos de reproducción están en
avx2-cve-2026-10097/ y
neon-cve-2026-6330/.
Este repositorio documenta dos hallazgos hermanos de la misma ventana de lanzamiento de wolfSSL, y un ataque que rompe ambos.
Ambos problemas se reportaron de forma privada, se corrigieron y se les asignaron CVEs antes de este informe. Corregidos en wolfSSL 5.9.2.
El informe completo se publica como
Cryptology ePrint Archive, Paper 2026/1682
(copia local: paper/).
@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}
}