FOUNDATIONS: what is an X.509 certificate and chain? (read first)
A certificate is a document, signed by a trusted party, that binds a public key to an identity (for example a server name). Instead of trusting a single certificate, a chain is built: a root signs itself and becomes the single point everyone trusts in advance; the root signs an intermediate certificate; and the intermediate signs the leaf certificate that is actually used. This three-level structure lets the root be used rarely and kept safe. If you read the x509-pki-refresher lesson this will be familiar; if not, this short summary is enough to follow this lesson.
Mental model
An X.509 certificate chain has three parts: root, intermediate, leaf. Each part carries two things: its own public key, and a signature showing that the authority above it signed that key. With RSA-2048 each signature is 256 BDERIVED and each public key about 256 BDERIVED; the whole chain is a few kilobytes. Moving to ML-DSA-65 does not just mean “a slightly larger number”: both the signature and the public key grow to many times RSA’s, and that growth repeats three times along the chain (the root signs itself, the intermediate is signed by the root, the leaf by the intermediate).
The figure this lesson gives may have appeared in three different forms in the research documents you have read so far: 12.5 KB, 13.8 KB, ~12 KB. All three seem to answer the same question, but none comes from a demonstrable command, or, if it does, it silently measures something different. The real subject of this lesson is not just “what is the right number” but “how do you know a number is right”.
The measurement
According to FIPS 204, which has FINAL2024-08-13 status, ML-DSA-65’s public key and signature sizes are:
- Public key: 1952 BSOURCED (7.63×DERIVED times the RSA-2048 public key)
- Signature: 3309 BSOURCED (12.93×DERIVED times the RSA-2048 signature)
These two numbers alone do not give a chain’s total size: a certificate carries not only a signature and a public key but also the subject name, validity dates, extensions (basicConstraints, keyUsage and so on) and the overhead of PEM base64 encoding itself. So the only way to find a chain’s real size is to actually build one and measure it.
While preparing this lesson, a root, an intermediate and a leaf certificate using ML-DSA-65 throughout were generated in a local environment (OpenSSL 3.6.2, macOS arm64, 2026-09-04), and the openssl x509 -noout -text output confirmed that all three really are ML-DSA-65 (in both the Public Key Algorithm and the Signature Algorithm fields). This check matters, because as you will see, in an earlier research round exactly this check was skipped and the leaf certificate was mistakenly generated with RSA-2048.
Result:
- 3 certificates (root + intermediate + leaf), PEM: 22945 BMEASURED
- 2 certificates without the root (most TLS servers do not send the root), PEM: 15306 BMEASURED
The same structure, the same command sequence, with RSA-2048:
- 3 certificates, PEM: 3739 BMEASURED
- 2 certificates, PEM: 2502 BMEASURED
Ratio: 6.14×DERIVED (3 certificates), 6.12×DERIVED (2 certificates). The two are close, as expected: the root certificate grows at a similar rate on both the PQC and RSA sides, so it does not change the ratio much.
Why three different figures were in circulation
An earlier research round on this topic produced three different figures, none from a demonstrable command like the one in this lesson:
~12.5 KB. Where it first appeared, no derivation is shown and it is not even stated which three certificates are meant. Repeated seven times, never with a source.
~13.8 KB, “corrected”. Presented in the next round as a “real measurement”, but it has two problems. First: the leaf certificate was generated with openssl genrsa, so it is really RSA-2048; only the intermediate and root are ML-DSA-65. What was labelled an “ML-DSA-65 three-certificate chain” is actually a mixed chain. Second: the 12.5 KB figure was a DER estimate while 13.8 KB is a PEM measurement, so apples were being compared with oranges and the result was called a “correction”.
~12 KB. In a third place, a third rounding appeared, independent of the previous two, also unsourced.
The figure this lesson gives, 22945 BMEASURED, replaces all three: one encoding (PEM), one composition (all three certificates really ML-DSA-65, verified), a demonstrable command, a recorded environment. In data/numbers.json the supersedes field of this key records exactly where and why each of the three earlier wrong figures (12.5 KB, 13.8 KB, ~12 KB) was wrong; it is a trail you can show the next time someone says “but I heard 13.8 KB”.
What we learned
A number being “corrected” does not automatically make it right. A correction must itself be questioned with the same discipline (how was it measured, in which environment, with which command). Every figure you will see in the rest of this course should have passed three questions: where did it come from, what did it really measure, and can you show it when someone asks.