Post-quantum key agreement arrives on test domains
Any data sent over the Internet today could be vulnerable to a future, sufficiently powerful quantum computer. Traffic that is captured now can be stored and decrypted later. Post-quantum (PQ) cryptography is designed to be resistant to such an attack, and in July 2022 NIST selected the Kyber key agreement scheme for standardization after a six-year global selection process. The standard is expected to be finalized in 2024. Until then, to help drive adoption and give developers an early look, support for the X25519Kyber512Draft00 and X25519Kyber768Draft00 hybrid post-quantum key agreements has been added to a set of test domains, including pq.cloudflareresearch.com.
Developers can enroll their own test domains in the beta by mailing [email protected]. There are a few caveats to understand beforehand.
How the beta affects your site
Enabling the PQ beta adds support for two additional key agreements, alongside existing classical schemes such as X25519. Browsers and clients that do not support the PQ key agreements simply continue with a classical, non-quantum-resistant connection—none of the current browsers support the PQ schemes yet, so existing traffic is unaffected.
What to test and why
Kyber and classical key agreements like X25519 have different performance profiles. Kyber requires less computation but produces larger keys and uses more RAM. Alone, it could even make a connection faster. However, the beta uses a hybrid approach: both X25519 and Kyber are run, so the connection remains classically secure if either is broken. This adds a small overhead, but internal experiments show the difference to be marginal. Developers should benchmark their own setups to verify the impact.
Availability and support constraints
This is a beta service for experimental use only. Enrolled zones are subject to Cloudflare's Beta Services terms. Note the following limitations:
- No stability guarantees: Kyber itself is still evolving, with small backward-incompatible changes expected. Cloudflare also intends to stay compatible with other early adopters and may change its integration accordingly. Forks will be updated, but support may be withdrawn at any time, with updates posted on the test domain.
- Limited feature set: Enrolled zones run on a different infrastructure that does not yet support all features, notably QUIC.
Hands-on testing
To test a PQ connection, you can use open-source forks of BoringSSL and Go. Both need an enrolled test domain.
Using BoringSSL
Build the BoringSSL fork, then connect to a test domain with the bssl tool. The PQ key agreements are not enabled by default, so pass the -curves flag to select them explicitly.
$ git clone https://github.com/cloudflare/boringssl-pq
[snip]
$ cd boringssl-pq && mkdir build && cd build && cmake .. -GNinja && ninja
[snip]
$ ./tool/bssl client -connect pq.cloudflareresearch.com -server-name pq.cloudflareresearch.com -curves Xyber512D00
Connecting to [2606:4700:7::a29f:8a55]:443
Connected.
Version: TLSv1.3
Resumed session: no
Cipher: TLS_AES_128_GCM_SHA256
ECDHE curve: X25519Kyber512Draft00
Signature algorithm: ecdsa_secp256r1_sha256
Secure renegotiation: yes
Extended master secret: yes
Next protocol negotiated:
ALPN protocol:
OCSP staple: no
SCT list: no
Early data: no
Encrypted ClientHello: no
Cert subject: CN = *.pq.cloudflareresearch.com
Cert issuer: C = US, O = Let's Encrypt, CN = E1
Using Go
The Go fork also requires opt-in for PQ. A small program that enables the PQ key agreement for the default HTTP client can be used to fetch pq.cloudflareresearch.com.
package main
import (
"context"
"crypto/tls"
"fmt"
"net/http"
)
func main() {
req, err := http.NewRequestWithContext(
context.WithValue(
context.Background(),
tls.CFEventHandlerContextKey{},
func(ev tls.CFEvent) {
switch e := ev.(type) {
case tls.CFEventTLS13HRR:
fmt.Printf("HelloRetryRequest\n")
case tls.CFEventTLS13NegotiatedKEX:
switch e.KEX {
case tls.X25519Kyber512Draft00:
fmt.Printf("Used X25519Kyber512Draft00\n")
default:
fmt.Printf("Used %d\n", e.KEX)
}
}
},
),
"GET",
"https://pq.cloudflareresearch.com",
nil,
)
if err != nil {
panic(err)
}
http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{
CurvePreferences: []tls.CurveID{tls.X25519Kyber512Draft00, tls.X25519},
}
if _, err = (&http.Client{}).Do(req); err != nil {
fmt.Println(err)
}
}
Compile and run the program with the Go fork:
$ git clone https://github.com/cloudflare/go
[snip]
$ cd go/src && ./all.bash
[snip]
$ ../bin/go run path/to/example.go
Used X25519Kyber512Draft00
What the handshake looks like
Capturing the TLS 1.3 handshake with Wireshark is illuminating. A standard X25519 connection looks as expected: the ClientHello carries only the X25519 keyshare and fits in a single packet, and the server responds with its own 32-byte keyshare plus certificate chain, spanning two packets.

The post-quantum handshake shows the difference. The ClientHello is larger but still fits in one packet. The server response grows to three packets due to the larger Kyber keyshare.

Implementation details
The hybrid approach combines X25519 with Kyber version 3.02, specified in version 00 of the corresponding CRFG IETF draft. The TLS group identifiers are 0xfe30 for X25519Kyber512Draft00 and 0xfe31 for X25519Kyber768Draft00.
The Go and BoringSSL forks differ in two practical ways:
- The Go fork uses the AVX2-optimized Kyber implementation from the CIRCL library, while the BoringSSL fork uses the portable reference implementation. The latter is simpler to evaluate but slower—though still very fast in absolute terms.
- The Go fork sends only one keyshare. If the server does not support it, a HelloRetryRequest triggers a fallback to a supported scheme, costing an extra roundtrip. The BoringSSL fork sends two keyshares—the PQ hybrid and a classical one—so the server can pick the second if it does not recognize the first, avoiding the extra roundtrip.
Feedback on the beta can be sent to [email protected]. This experiment is an early step toward wider post-quantum adoption, and further announcements are expected as the ecosystem evolves.



