
Recuperação completa da chave ML-KEM-1024 a partir de uma comparação parcial de Fujisaki-Okamoto no wolfSSL (CVE-2026-6330 NEON, CVE-2026-10097 AVX2)
CVE-2026-6330 (NEON) e CVE-2026-10097 (AVX2). Corrigido no wolfSSL 5.9.2.
O artigo técnico completo está publicado como
Cryptology ePrint Archive, Paper 2026/1682
(cópia local em paper/). Veja Citação abaixo.
O ML-KEM (Kyber, FIPS 203) resiste a ataques de texto cifrado escolhido por causa de uma verificação dentro da decapsulação: o receptor re-criptografa a mensagem que acabou de descriptografar e retorna o segredo compartilhado real somente se o resultado corresponder ao texto cifrado de entrada exatamente. Qualquer incompatibilidade aciona a rejeição implícita, e um valor pseudoaleatório é retornado em seu lugar. Essa verificação é a transformada de Fujisaki-Okamoto, a única porta que separa o esquema IND-CCA2 do esquema maleável subjacente.
O wolfSSL implementou essa comparação em assembly SIMD escrito à mão, e em dois backends ele comparou apenas parte do texto cifrado. No ARM64 NEON ele comparou cerca de metade, o que Nicholas Carlini (Wikipedia) encontrou e relatou (CVE-2026-6330). No x86-64 AVX2 ele comparou 1536 dos 1568 bytes no ML-KEM-1024 (CVE-2026-10097).
Em qualquer um dos backends, um texto cifrado adulterado é aceito como genuíno, o que foi relatado como um
enfraquecimento da segurança IND-CCA2. Este artigo mostra que a falha vai além, em ambos os
backends. Os bytes que a verificação ignora vazam o ruído de descriptografia da decapsulação, e esse
ruído é uma função linear da chave secreta, então a chave pode ser recuperada por
regressão de mínimos quadrados. Não é um
problema de reticulado, e não é o ataque clássico de incompatibilidade de chave: no backend AVX2 a
falha valida todo o u, então os textos cifrados com u escolhido que esses ataques exigem são rejeitados.
A regressão do ruído de descriptografia, em vez disso, recupera a chave dos coeficientes v não verificados.
A chave privada completa do ML-KEM-1024 é recuperada de ponta a ponta contra o binário vulnerável em execução
em ambos os backends AVX2 e NEON.
O ataque precisa de uma chave ML-KEM reutilizada: destinatários HPKE, KEMTLS, ou chaves fixadas e incorporadas. Um compartilhamento de chave híbrido efêmero TLS 1.3 usa uma chave nova por handshake e não é recuperável dessa forma; nesse caso, a falha é apenas uma quebra de distinção. Ambas as falhas são corrigidas e públicas, e este é um artigo pós-divulgação.
| NEON (CVE-2026-6330) | AVX2 (CVE-2026-10097) | |
|---|---|---|
| Backend | ARM64 NEON | x86-64 AVX2 |
| Falha | compara ~metade do texto cifrado | compara 1536 de 1568 bytes |
| Bug relatado por | Nicholas Carlini (Anthropic) | 007bsd |
| Gravidade do CVE | Média (CVSS 4.0 6.3, CWE-327) | Alta (CVSS 4.0 8.3, CWE-697) |
| Recuperação de chave, modelo | completa (~500 ct) | completa (~1300 ct) |
| Recuperação de chave, binário em execução | 98,5% @ 600 ct (emulado via QEMU) | 98,1% @ 350 ct (nativo) |
| Correção | PR #10192 | PR #10430 |
"em execução" é o resultado demonstrado no binário real: o NEON precisa de mais textos cifrados (600 vs 350) porque sua medição por coeficiente é muito mais ruidosa (cerca de 4x). "modelo" é uma verificação sem ruído de que a regressão recupera a chave completa (todos os 2048 coeficientes, 100%); sua contagem de textos cifrados é definida por quantas equações cada texto cifrado produz, não pelo ruído, então não é comparável ao valor em execução e não reflete a dificuldade do ataque.
Ambas as falhas foram encontradas independentemente na mesma janela de lançamento do wolfSSL e corrigidas juntas
no 5.9.2 (veja a
página de vulnerabilidades de segurança do wolfSSL para
ambas as entradas de CVE). O bug NEON de Carlini foi documentado como um enfraquecimento IND-CCA2, e o bug AVX2 é
registrado como CVE de recuperação de chave. O ataque de regressão do ruído de descriptografia neste repositório recupera
a chave em ambos. Detalhes específicos de cada backend estão em
neon-cve-2026-6330/ e
avx2-cve-2026-10097/.
Uma comparação incompleta na verificação de rejeição implícita do ML-KEM do wolfSSL, presente em dois backends de assembly SIMD. Ela comparou menos do que todos os bytes do texto cifrado, então a decapsulação aceita textos cifrados que uma implementação correta rejeita. Os bytes não verificados transformam a decapsulação em um oráculo para o ruído de descriptografia, e isso é suficiente para recuperar a chave privada quando a chave é reutilizada.
Você é afetado se usou o wolfSSL com ML-KEM em uma compilação com o assembly SIMD vulnerável: AVX2 (x86-64) em 5.7.0-5.9.1, ou ARM64 NEON em 5.7.4-5.9.1. O ataque de recuperação de chave adicionalmente precisa que a chave privada ML-KEM seja reutilizada entre decapsulações, como com destinatários HPKE, KEMTLS, ou uma chave fixada ou incorporada. Um compartilhamento de chave híbrido efêmero TLS 1.3 usa uma chave nova por handshake e não é recuperável; nesse caso, a falha é apenas uma quebra de distinção. A correção está no wolfSSL 5.9.2.
Recuperar toda a chave secreta do ML-KEM-1024 em qualquer um dos backends, enviando consultas de decapsulação elaboradas e observando, para cada uma, se o segredo real ou um valor de rejeição retorna. Contra os binários em execução, isso recuperou a chave com aproximadamente 10^5 a 10^6 consultas de decapsulação.
Pegue um coeficiente de texto cifrado na região não verificada. Seu bit de texto simples é decidido por
arredondamento, m'_j = Compress_1(v_j - (s^T u)_j). Como esses bytes não são comparados,
varrer o valor comprimido de v_j e observar a única saída de decapsulação que
muda localiza um limite de arredondamento, e a posição do limite mede o ruído de descriptografia
δ_j com uma precisão de cerca de ±q/64. Em termos do segredo (s, e) e da
aleatoriedade de criptografia que o atacante escolheu:
δ_j = ( e^T y - s^T(e1 + c_u) + e2 + c_v )_j (verificado exato; pequeno; nunca envolve mod q)
Essa é uma equação linear no segredo de 2048 coeficientes (s, e), com coeficientes que o atacante
conhece. Cada texto cifrado dá uma dessas equações por coeficiente não verificado. Empilhando
textos cifrados suficientes e resolvendo as equações normais recupera toda a chave, e os últimos
poucos coeficientes são fixados pela relação de chave pública e = t - A·s ∈ CBD(η). É
mínimos quadrados ordinários, não um
problema de reticulado.
Os dois backends diferem em quanto vazam. O NEON deixa cerca de 50% do texto cifrado não verificado (125 coeficientes em três faixas), contra cerca de 2% para o AVX2 (51 coeficientes em uma faixa). O NEON ainda precisa de mais textos cifrados, porque seus bytes não verificados são não contíguos e sua medição por coeficiente é mais ruidosa, então vazar mais não torna a recuperação mais fácil aqui.
Ataques de verificação de texto simples e incompatibilidade de chave (Ravi et al. 2020, Qin et al. 2021, Băetu et
al. 2019) recuperam a chave em alguns milhares de consultas enviando textos cifrados com
u esparso escolhido pelo atacante para isolar um coeficiente secreto por consulta. A falha AVX2
rejeita todos eles: ela pula apenas 32 bytes de v e valida todo o u, então no
binário em execução um texto cifrado com u esparso é rejeitado e inverter um único bit de u é
rejeitado 30 vezes em 30. A falha NEON é mais ampla, deixando cerca de metade de u não verificada
também, então esse argumento é específico do AVX2; mas o ataque não depende disso. A
regressão do ruído de descriptografia recupera a chave dos coeficientes v não verificados em ambos
os backends, sobrevivendo à parte da verificação que cada bug deixa intacta.
O precedente relevante, então, não são esses ataques de incompatibilidade de chave, mas ataques de falha de descriptografia (D'Anvers et al. 2019, ref 6). Eles exploram o mesmo termo de ruído de descriptografia, uma função linear do segredo, mas apenas através do evento raro desse termo cruzar o limite de decodificação e causar uma falha de descriptografia, então a recuperação lá é estatística e precisa de muito mais textos cifrados. A comparação incompleta, em vez disso, torna esse mesmo termo diretamente mensurável, para cerca de ±q/64, então nenhuma falha é necessária e o ataque colapsa na regressão de mínimos quadrados ordinários acima.
Em um modelo de referência fiel (kyber-py) para ambos os backends, a identidade acima é verificada exata e a regressão recupera todos os 2048 coeficientes secretos. Contra os binários wolfSSL em execução pré-correção, atacando uma chave exportada reutilizada, cada harness executa uma autoverificação contra a chave de verdade fundamental exportada do oráculo antes de relatar qualquer número:
(s, e) em 400 textos cifrados. O segredo s sozinho é 1005/1024 = 98,1% em
350 ct, o valor registrado no CVE.(s, e) em 600 textos cifrados, com o erro do solucionador convergindo
monotonicamente para exato.A chave recuperada é o par de chaves gerado pelo wolfSSL que o oráculo exporta, e o ataque roda
contra o assembly SIMD na forma distribuída, em vez de um modelo dele. Transcrições completas:
avx2-cve-2026-10097/live_recover_avx2.out,
neon-cve-2026-6330/live_recover_neon.out.
Alto, embora não catastrófico. Precisa de uma chave reutilizada, um oráculo de aceitar/rejeitar, e um número grande mas prático de consultas. Não é um canal lateral: não há medição de tempo ou energia, apenas um bug lógico em uma comparação. Não afeta implementações corretas ou o próprio padrão ML-KEM. Para comparação, um vazamento de tempo induzido por compilador na decapsulação ML-KEM do liboqs (CVE-2024-36405), uma questão relacionada de recuperação completa de chave secreta da onda de 2024 de vazamentos de tempo do ML-KEM junto com KyberSlash, foi avaliado como CVSS 7.5 pelo NIST.
Código de ataque por backend, análise e etapas de reprodução estão em
avx2-cve-2026-10097/ e
neon-cve-2026-6330/.
Este repositório documenta duas descobertas irmãs da mesma janela de lançamento do wolfSSL, e um ataque que quebra ambas.
Ambas as questões foram relatadas privadamente, corrigidas e receberam CVEs antes deste artigo. Corrigidas no wolfSSL 5.9.2.
O artigo completo está publicado como
Cryptology ePrint Archive, Paper 2026/1682
(cópia 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}
}