Hi Nico,
On 7/8/26 18:23, Nico Williams wrote:
On Wed, Jul 08, 2026 at 04:04:34PM +1000, Viktor Dukhovni wrote:
On Tue, Jul 07, 2026 at 10:27:56PM -0700, Christian Huitema wrote:
I just read Jacob Applebaum's message. Given his description of the
late-standardization suspicious change that looks like a backdoor in the
ML-KEM specification, I agree with his conclusion. The WG should not ask for
publication of the current graph, not until the changes requested by Jacob
are made.
The removal of whitening of the `m` random input to Encaps is not a
plausible backdoor. If all you have is a broken RNG, you're free to
apply whitening to obtain a new less bad RNG and use that instead.
Furthermore, `m` is not a covert channel as Jacob said because it
doesn't go in the clear on the wire.
This opens a minor semantic debate that rests on not having a shared
definition of a covert channel.
I will pick NIST for a definition [0] as FIPS 203 is a NIST document and
it is the discussion topic. They define a covert channel as "An
unintended or unauthorized intra-system channel that enables two
cooperating entities to transfer information in a way that violates the
system's security policy but does not exceed the entities' access
authorizations."
I would say that this definition is reached when we discuss `m`. The
decapsulating peer can recover `m` by instrumenting its own
decapsulation. If `m` is raw Dual_EC_DRBG output and that peer has the
Dual_EC trapdoor/secret key(s), it may be able to recover the sender's
DRBG state. This is certainly unintended and RNG state recovery arguably
violates the system's security policy.
To your point, there are layers to the problem. The transport layer
should be hardened against these kinds of leaks. The goal of leaking RNG
state to the wire ala Extended Random can probably be met in practice
because of a lack of defense-in-depth.
TLS 1.3 itself does not provide defense-in-depth as a MUST against
leaking information directly from the system random number generator to
the wire as far as I understand the current situation. If we look at the
tlslite-ng library[1][2] we see that after following the implementation
the raw bytes from os.urandom() are written to fields which will appear
on the wire. The example cited in [2] specifically says this is to fool
middle boxes.
If instead the 32 byte value was computed more defensively, we wouldn't
have to worry if those middle boxes could do anything with the otherwise
opaque random bytes. Not all middle boxes are equal and some are run by
large-scale adversaries.
If instead of `public_random = os.urandom(32)` which is immediately
written to the wire, we could compute `public_random` differently:
fresh_rng_bytes=os.urandom(32)
public_random = SHAKE256(
"TLS 1.3 public hello random" ||
role ||
fresh_rng_bytes ||
transcript_context
)[0:32]
Careful consideration should be taken when deciding exactly what to hash
but it doesn't take much consideration to consider that hashing is a
good idea. Hashing would destroy the structure that could conceivably
hide Dual_EC_DRBG in the latent covert channels in TLS 1.3.
OpenSSL’s TLS (client/server) hello random generation [3] uses RAND
output directly for values sent in the handshake; in deployments using a
validated provider/module, the underlying RAND implementation may be
provider/module dependent. Do we know of any problematic modules?
Perhaps a FIPS module? I will omit a similar C example for brevity.
Please note: I am not claiming a vulnerability in OpenSSL here. Rather,
I observe a gap that there isn't a standard, say an IETF standard, and
uniform way to not leak system randomness in the relevant protocol
specification(s).
I suspect that you may agree that if Dual_EC_DRBG was used to fill those
random fields, a passive adversary with the proper Dual_EC_DRBG
trapdoor/secret key(s) could begin to attack TLS in a meaningful manner.
Now on to ML-KEM. The fact is that ML-KEM provides a similar 32 byte
covert channel but to a slightly different adversary.
We agree that passive eavesdroppers do not see `m`. The issue is
different: the encapsulating endpoint samples `m`, and the decapsulating
endpoint can recover `m` internally. If `m` is raw output from a
Dual_EC_DRBG-shaped RNG, then an adversarial decapsulating peer with the
corresponding trapdoor may obtain an RNG-state-recovery oracle. Hashing
the RNG output before using it as `m`, as Kyber did, destroys that
algebraic structure.
Simplifying TLS 1.3 to clarify the ML-KEM relevant steps:
- a TLS 1.3 client generates their ML-KEM keypair which for simplicity
we will call `sk` and `pk`
- the TLS 1.3 client then sends an ML-KEM public key `pk` as part of TLS 1.3
- the TLS 1.3 server will then ML-KEM.Encaps(), sample their system
random number generator into `m` without whitening the RNG output, and
then they will encapsulate to the TLS 1.3 client's `pk`, producing
ciphertext `ct`
- the TLS 1.3 server sends the ciphertext `ct` over to the TLS 1.3 client
- the TLS 1.3 client now decapsulates the ciphertext `ct` using its
ML-KEM `sk` using a modified `ML-KEM._decaps_internal()` that saves the
result of `ML-KEM._k_pke_decrypt(dk_pke, ct)` for analysis
- the TLS 1.3 client continues the TLS1.3 handshake or breaks off the
communication with the server
- with Encrypted Client Hello (ECH) configured to use ML-KEM or a hybrid
KEM, ML-KEM encapsulation can happen in both directions
Now it is true that an adversary wishing to sample `m` in either
direction would need to modify their ML-KEM.Decaps() function or rather
the `ML-KEM._decaps_internal()` function as the `m` is indeed not part
of the public API. It is however up to the TLS 1.3 client or the TLS 1.3
server to decide if they'd like to use a private API. It was up to NIST
if the server should hash `m` and they removed the hash.
ML-KEM-compliant TLS 1.3 servers that sample `m` directly from a
Dual_EC_DRBG-shaped RNG provide this oracle on demand.
This general concern was pointed out in public [4] in April of 2023 by
Prof. Dr. Peter Schwabe. It is too bad that he was ignored and even
worse is that this was not systematically addressed. Defense against a
possible timing attack is reasonable but defense against the
cryptographic sabotage previously _standardized by NIST_ that is still
in shipping software seems at least as relevant. Hardware is also a
concern and we know that hardware is targeted for clever sabotage [5].
Since `m`'s confidentiality is
critical to the security of ML-KEM, if `m` leaked in a covert channel,
that would destroy ML-KEM's security, but that's why `m` is part of the
construction of ML-KEM's `ct` payload, and it gets encrypted to the `pk`
along the way, and then the peer doesn't surface `m` to the application
either, therefore:
However what is missing is that if you are leaking the output of say, a
hardware random number generator that is backed by Dual_EC_DRBG - one
`m` revealing the state of the RNG is likely to impact other `m` values.
This will depend on details of the implementation of the kleptography
but why leave it up to the large-scale adversaries? Just hash `m` and be
done with it.
- no eavesdropped gets to see `m`
The encapsulating endpoint samples `m`; the decapsulating endpoint can
recover `m` internally.
- `m` is not a covert channel
I am sorry to say that `m` is a covert channel when the covert channel
is between a backdoor and an Adversary. A normal endpoint without the
Dual_EC_DRBG secret key(s) may see `m` without recognizing the RNG state
encoded in it; meanwhile the Dual_EC_DRBG secret key(s) holder can
probably recover that state.
- hashing or not hashing the RNG output that gets used as `m` makes no
difference and nothing can be leaked due to not hashing it
Hashing the RNG output before using it as `m` changes the value
encrypted by ML-KEM and destroys the Dual_EC_DRBG algebraic structure
before the decapsulating peer can observe it. That is a difference and
when the plaintext is raw RNG output that enables state recovery, that
may give an advantage to the Adversary in every subsequent RNG output
until reseeding. For Dual_EC_DRBG that is certainly the case with the
correct RNG leakage.
And being a KEM, the two parties both contribute entropy, so a poor
choice of RNG on the server will not compromise the whole session.
These are building blocks. You are describing a larger attack but I am
surprised that without seeing the smaller attacks, you assert that the
larger attacks are... not possible?
But let's say one wants to hash the RNG outputs, then what has one
achieved? This: that one has merely altered the RNG design.
In a sense, yes: This alters the RNG design. This is good when the RNG
isn't designed in our favor but to favor an adversary.
In particular, if your hardware RNG is producing structured
Dual_EC_DRBG-shaped output that can be inverted by a Dual_EC_DRBG secret
key(s) holder, hashing would block that kleptographic channel.
Recovering Dual_EC_DRBG state relies on preserving the structured
x-coordinate output; hashing destroys that structure.
So what has one achieved by hashing?
For one it has almost certainly destroyed the structure that is
available to an adversary by merely querying a (TLS or another protocol
with a similar ML-KEM.Encaps() oracle) server. Prof. Dr. Peter Schwabe
noted this [4] in April of 2023 as well.
Perhaps we do not yet agree, perhaps the hypotheticals in this email
land, perhaps it isn't relevant to you. Regardless this concern belongs
in the same historical context as NSA attacks on deployed Internet
cryptography[6], including IPsec and TLS.
For the people who aren't careful readers or who will try to
misrepresent the nuance in this discussion: I am not claiming a backdoor
in ML-KEM. I am restating that the removal of Kyber's mitigation against
a specific class of kleptography is a catastrophic mistake waiting to
happen, especially when combined with TLS 1.3. The lattice hardness
assumptions would be completely irrelevant to the Adversary described.
Kind regards,
Jacob Appelbaum
P.S.
As a fun aside - consider that hashing also mitigates the post-quantum
concerns inherent in Dual_EC_DRBG's design. If and when Q day comes, I
know the first ECDLP problems I'd want to solve after Satoshi's keys:
NIST:
'c97445f45cdef9f0d3e05e1e585fc297235b82b5be8ff3efca67c59852018192'
'b28ef557ba31dfcbdd21ac46e2a91e3c304f44cb87058ada2cb815151e610046'
Juniper originally:
'9585320eeaf81044f20d55030a035b11bece81c785e6c933e4a8a131f6578107',
'5042b7e66d66a7b38582406a6c19dfa28b2e236038c6c9a78dd91cf4a03c3dda',
Juniper later:
'2c55e5e45edf713dc43475effe8813a60326a64d9ba3d2e39cb639b0f3b0ad10',
'de9d6f0a151d6d34761bcf35cb11e10de2eb7f7eda212c46473939db2a40ffd4',
[0] https://csrc.nist.gov/glossary/term/covert_channel
[1]
https://github.com/tlsfuzzer/tlslite-ng/blob/02d1506badb16473faf50ebc3a413c6d789fe31f/tlslite/utils/cryptomath.py#L103
[2]
https://github.com/tlsfuzzer/tlslite-ng/blob/02d1506badb16473faf50ebc3a413c6d789fe31f/tlslite/tlsconnection.py#L798
[3]
https://github.com/openssl/openssl/blob/a17cd7691ab33192abbcfe6a69aafb079ec1d0eb/ssl/s3_lib.c#L5252
[4]
https://groups.google.com/a/list.nist.gov/g/pqc-forum/c/WFRDl8DqYQ4/m/o2XJ2YvfAwAJ
[5]
https://www.computerweekly.com/news/366552520/New-revelations-from-the-Snowden-archive-surface
[6]
https://www.spiegel.de/international/germany/inside-the-nsa-s-war-on-internet-security-a-1010361.html
_______________________________________________
TLS mailing list -- [email protected]
To unsubscribe send an email to [email protected]