Kyber KEM

My previous post discussed the introduction of a Module-Lattice Key Encapsulation Mechanism (ML-KEM) post-quantum key exchange algorithm implementation in OpenSSH, and how to specify it in your SSH configurations. ML-KEM is the new FIPS 203 standard, previously known as Kyber, which was standardized by NIST on 2024-08-13. OpenSSH’s implementation of ML-KEM is a PQ/T hybrid approach that combines the post-quantum ML-KEM with the traditional X25519 key exchange algorithm. This post will cover the ML-KEM implementation in Caddy, the popular web server and reverse proxy.

Caddy v2.10.0, released on 2025-04-18, introduced support for ML-KEM as well, by default, no less. Caddy is using the PQ/T hybrid X25519MLKEM768 cryptographic group, similar to what OpenSSH has chosen to implement. There is currently an IETF draft which aims to formally standardize this hybrid approach (and two others) for use in TLS 1.3.

The industry seems to first move towards PQ/T hybrid algorithms, which are combining both post-quantum and traditional cryptographic algorithms. This aims to build trust first in these new post-quantum algorithms which have not yet withstood the test of time. Recall from my previous post that the benefit of a PQ/T hybrid approach is that, in the event that the post-quantum algorithm ends up being broken after further cryptanalysis, the traditional algorithm will still provide a secure fallback. At least for the near future, while there is no Cryptographically Relevant Quantum Computer (CRQC) yet that can break traditional cryptography such as X25519.

Chances are that your updated Caddy instance is already using the ML-KEM key exchange algorithm, but you can still explicitly declare this in the tls directive in your Caddyfile:

tls {
    protocols tls1.3
    curves x25519mlkem768
}

Once configured, you can verify that Caddy is indeed using the ML-KEM key exchange algorithm by checking the TLS handshake details. OpenSSL 3.5.0, released on 2025-04-08, introduced support for this new key exchange algorithm, and you can use the openssl s_client command to verify if Caddy is using it:

user $ openssl s_client -connect example.org:443 -groups X25519MLKEM768 -brief < /dev/null
Connecting to 123.123.123.123:443
CONNECTION ESTABLISHED
Protocol version: TLSv1.3
Ciphersuite: TLS_CHACHA20_POLY1305_SHA256
Peer certificate: CN=example.org
Hash used: SHA256
Signature type: ecdsa_secp256r1_sha256
Verification: OK
Negotiated TLS1.3 group: X25519MLKEM768
DONE

Notice the Negotiated TLS1.3 group: X25519MLKEM768 line, indicating the new key exchange algorithm is being used.

Firefox 132.0, released on 2024-10-29, introduced this new key exchange algorithm as well. Or, if you’re using a different browser, you can verify if your browser supports a post-quantum key exchange algorithm by checking Cloudflare’s PQ test page.

Caddy also introduced Encrypted ClientHello (ECH) support in the same release. I was also hoping to write about ECH as well, but I will have to postpone that for a later date. Since I’m using PowerDNS as my preferred authoritative DNS server, and since ECH relies on publishing configurations in DNS HTTPS RRs, I’m still waiting for when the PowerDNS module for Caddy has been fixed. Currently it does not build, so, yeah…​

Slightly off-topic, but DNS integration in the certificate renewal process (which Caddy also takes care of) is something I have long been looking forward to, as it that will hopefully also allow us to automatically roll-over TLSA RRs when using DANE.