Skip to content

TLS 1.3: add hybrid post-quantum key exchange support (X25519MLKEM768)#472

Merged
dadrian merged 8 commits intozmap:masterfrom
UnaPibaGeek:pqc_mlkem_support
Feb 18, 2026
Merged

TLS 1.3: add hybrid post-quantum key exchange support (X25519MLKEM768)#472
dadrian merged 8 commits intozmap:masterfrom
UnaPibaGeek:pqc_mlkem_support

Conversation

@UnaPibaGeek
Copy link
Copy Markdown
Contributor

Summary

This PR adds support for the TLS 1.3 hybrid post-quantum key exchange X25519MLKEM768 (IANA group 4588) to zcrypto.

The implementation enables zcrypto (and tools depending on it, such as zgrab2) to successfully negotiate and detect post-quantum-ready TLS 1.3 deployments, while fully preserving compatibility with classical TLS handshakes (TLS 1.0/1.1/1.2/1.3 with ECDHE).


Motivation

Large CDNs (e.g. Cloudflare and others) already support X25519MLKEM768, which currently represents a significant fraction of global HTTPS traffic.

From a measurement and research perspective, this makes it important to:

  • Detect whether TLS services are post-quantum capable
  • Identify which hybrid key exchange groups are negotiated in practice
  • Measure real-world adoption and readiness for the post-quantum era

I'm currently performing this research and measurement, therefore I developed this MLKEM support for zcrypto.


Technical details

  • Implements TLS 1.3 hybrid key exchange X25519MLKEM768 (group 4588).
  • ClientHello advertises:
    • Hybrid ML-KEM + X25519 key share
    • Classical X25519 fallback key share
  • The handshake correctly derives the shared secret as: shared_secret = ML-KEM_shared_secret || ECDHE_shared_secret in accordance with current TLS hybrid PQ designs.
  • Full support for:
    • Normal TLS 1.3 handshakes
    • HelloRetryRequest (HRR)
    • Classical TLS 1.3 fallback

The implementation uses Go's standard crypto/mlkem package and does not introduce any external dependencies.


Compatibility and safety

  • No behavior changes for TLS 1.2 or earlier versions.
  • TLS 1.3 without post-quantum support continues to work unchanged.
  • Hybrid key exchange is strictly opt-in and negotiated only if the server explicitly selects it.
  • A classical ECDHE fallback is always advertised to ensure compatibility with non-PQ-capable servers.

Extensive testing was performed against:

  • TLS 1.2 servers
  • TLS 1.3 classical ECDHE servers
  • TLS 1.3 servers supporting X25519MLKEM768

All scenarios complete successfully.

A recommendation could be to support the curve preference parameter in zgrab2 so that users can exclude X25519MLKEM768 if they don't want to attempt to negotiate it. However, as mentioned, this PR does not affect compatibility with non-PQ-capable services.

@UnaPibaGeek
Copy link
Copy Markdown
Contributor Author

UnaPibaGeek commented Jan 29, 2026

The implementation works, but certain testdata for handshake_client_test.go tests is now legacy and appear to require updates to pass. I'm unable to update the tls/testdata locally yet 🤔

@Vytek
Copy link
Copy Markdown

Vytek commented Feb 10, 2026

@UnaPibaGeek I'm very interested in your work because I'd like to read and understand PQC Compliant systems. zcrypto is also used by zgrab and tlsx (projectdiscovery/tlsx#880). How can I help?

@UnaPibaGeek
Copy link
Copy Markdown
Contributor Author

@UnaPibaGeek I'm very interested in your work because I'd like to read and understand PQC Compliant systems. zcrypto is also used by zgrab and tlsx (projectdiscovery/tlsx#880). How can I help?

This implementation already detects servers that prefer to use ML-KEM. You can use this branch with zgrab2, and you will see ML-KEM in the selected key_exchange for those servers that prefer it. If you inspect the connection with Wireshark, you can see the full negotiation using ML-KEM. The problem is that the current unit tests are not happy with it. I will try to make ML-KEM optional with a flag or something similar, so that the current unit tests can pass and ideally be merged.

@UnaPibaGeek
Copy link
Copy Markdown
Contributor Author

Yay! All checks have passed...

I now opened this PR in the zgrab2 repository to add the --enable-mlkem parameter so the user can enable it manually: zmap/zgrab2#672.

Using echo "example.com" | ./zgrab2 http --use-https --port 443 --enable-mlkem will enable the hybrid post-quantum key exchange group X25519MLKEM768 as the first "curve" preference.

@Vytek
Copy link
Copy Markdown

Vytek commented Feb 16, 2026

Thank you, thank you very much!!! This is a very important and useful feature! Thank you!

@Vytek
Copy link
Copy Markdown

Vytek commented Feb 17, 2026

@UnaPibaGeek I wrote an email to the zgrab2 maintainers asking them to integrate this PR as soon as possible...

Copy link
Copy Markdown
Member

@dadrian dadrian left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably update the default ciphers as well

@dadrian dadrian merged commit 1e860df into zmap:master Feb 18, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants