PQ Crypta Logo

Hybrid key share with an intact X25519 half and a corrupt ML-KEM half

Fail the handshake. The hybrid secret is the concatenation of both shares fed through the key schedule, so corrupting either half must produce a transcript mismatch and a failed Finished verification. What is being looked for is the failure mode, not the failure: a client that falls back to the classical half alone has silently downgraded itself to exactly the security level the hybrid exists to avoid, and would do so against an attacker who can corrupt one half at will. The server share for X25519MLKEM768 is the 1,088-byte ML-KEM ciphertext followed by the 32-byte X25519 key. One bit is flipped early in the ciphertext and the classical tail is left untouched, so a client that still completes has used the classical half alone. A single bit rather than a scribble on purpose: ML-KEM decapsulation never fails, it returns an implicit-rejection secret, so the handshake has to die at Finished verification rather than at a decode error — damaging the length or the structure would test the parser instead.

PQ CRYPTA PLATFORM

🏠 Main

📰 News

👤 Account

⟨ QUANTUM ERROR PORTAL ⟩

Navigate the Error Dimensions

Jump to an anomaly59

Every one of these is a page: what the server emits, the clause it is judged against, and how each client answered. The full list carries the verdict tallies too.

t-corrupt-hybrid-share · draft-ietf-tls-hybrid-design §3.2, RFC 8446 §4.1.3 · correctness · layer tls · udp/4515 · MUST

Passing this means: Rejected something invalid, with the code the RFC names.

Anomaly type: transport

How each client answered

ClientVersionVerdictWhat happened
curl libcurl/8.23.0 ngtcp2/1.25.0 Pass Responded correctly: did not complete the handshake (timed out). The shared secret is both halves through the key schedule, so a corrupt ML-KEM half must break it -- and this client did not fall back to the intact X25519 half.
quinn noq fork (189b0ed) Pass Responded correctly: did not complete the handshake (timed out). The shared secret is both halves through the key schedule, so a corrupt ML-KEM half must break it -- and this client did not fall back to the intact X25519 half.
aioquic 1.3.0 (PyPI wheel) Unsupported The client offers no key exchange group this port will negotiate, so it was refused before a handshake existed (the cryptographic handshake failed: error 40: peer is incompatible: NoKxGroupsInCommon). That is a capability this client does not have, rather than something the run failed to measure.
chromium build 1248 Pass Responded correctly: did not complete the handshake (timed out). The shared secret is both halves through the key schedule, so a corrupt ML-KEM half must break it -- and this client did not fall back to the intact X25519 half.
quic-go quic-go v0.61.0 (h1:9So2anK4Tp22) Pass Responded correctly: did not complete the handshake (timed out). The shared secret is both halves through the key schedule, so a corrupt ML-KEM half must break it -- and this client did not fall back to the intact X25519 half.
quiche 0.30.0 (1c8e976) Pass Responded correctly: did not complete the handshake (timed out). The shared secret is both halves through the key schedule, so a corrupt ML-KEM half must break it -- and this client did not fall back to the intact X25519 half.
neqo 0.31.1 (cac2b74) Pass Responded correctly: did not complete the handshake (timed out). The shared secret is both halves through the key schedule, so a corrupt ML-KEM half must break it -- and this client did not fall back to the intact X25519 half.
picoquic 8162550 Pass Responded correctly: did not complete the handshake (timed out). The shared secret is both halves through the key schedule, so a corrupt ML-KEM half must break it -- and this client did not fall back to the intact X25519 half.
ngtcp2 3c23148 Pass Responded correctly: did not complete the handshake (timed out). The shared secret is both halves through the key schedule, so a corrupt ML-KEM half must break it -- and this client did not fall back to the intact X25519 half.
lsquic 4.9.4 (2b30189) Pass Responded correctly: did not complete the handshake (timed out). The shared secret is both halves through the key schedule, so a corrupt ML-KEM half must break it -- and this client did not fall back to the intact X25519 half.
xquic 2060a44 Pass Responded correctly: rejected the handshake with TLS alert 40. The shared secret is both halves through the key schedule, so a corrupt ML-KEM half must break it -- and this client did not fall back to the intact X25519 half.
msquic 2.7.0 (7e6525d) Unsupported The client offers no key exchange group this port will negotiate, so it was refused before a handshake existed (the cryptographic handshake failed: error 40: peer is incompatible: NoKxGroupsInCommon). That is a capability this client does not have, rather than something the run failed to measure.
quico 0.7.0 (npm) Pass Responded correctly: did not complete the handshake (timed out). The shared secret is both halves through the key schedule, so a corrupt ML-KEM half must break it -- and this client did not fall back to the intact X25519 half.

What the clause requires

Fail the handshake. The hybrid secret is the concatenation of both shares fed through the key schedule, so corrupting either half must produce a transcript mismatch and a failed Finished verification. What is being looked for is the failure mode, not the failure: a client that falls back to the classical half alone has silently downgraded itself to exactly the security level the hybrid exists to avoid, and would do so against an attacker who can corrupt one half at will. The server share for X25519MLKEM768 is the 1,088-byte ML-KEM ciphertext followed by the 32-byte X25519 key. One bit is flipped early in the ciphertext and the classical tail is left untouched, so a client that still completes has used the classical half alone. A single bit rather than a scribble on purpose: ML-KEM decapsulation never fails, it returns an implicit-rejection secret, so the handshake has to die at Finished verification rather than at a decode error — damaging the length or the structure would test the parser instead.

Judged against RFC 8446.

Run this one yourself

Every test is reachable from your own client. Nothing here needs an account.

curl -s https://conformance.pqcrypta.com/catalog.json \
  | jq '.tests[] | select(.id == "t-corrupt-hybrid-share")'