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]

Reply via email to