# The Great Certificate Size Explosion – IPsec Series, Part 7

> Source: <https://blogs.cisco.com/developer/the-great-certificate-size-explosion-ipsec-series-part-7>
> Published: 2026-08-17 14:00:19+00:00

In Part 6 we met the post-quantum signatures and learned *why* authentication has a sneakier quantum deadline than key exchange. Now let’s get tactile. We’re going to generate real post-quantum keys and certificates with our own hands, line them up next to the classics, and **weigh them**. The size story is the single most important thing to internalise about post-quantum authentication, and the best part is you measure it yourself with nothing fancier than `ls`

. Everything runs in a throwaway `alpine`

container with OpenSSL, so all you need is Docker.

## Tooling

** OpenSSL 3.5+** is the first mainstream OpenSSL with

**native** support for all three NIST PQC algorithms (ML-KEM, ML-DSA, and SLH-DSA) built right in. No external plugins, no patches, no extra add-ons to install. If you’ve got 3.5 or newer, you generate post-quantum keys and certificates with plain, ordinary OpenSSL commands, the same kind you’d use for any classical key (check your version with

`openssl version`

).**Wait, if OpenSSL does ML-KEM, why did we use strongSwan for the key exchange?** Fair question. OpenSSL is a crypto *library*, not a VPN; its PQC support is wired into **TLS and certificates**, not IKEv2. So OpenSSL is perfect for *making and inspecting* the keys and certificates here, but it can’t run an IKEv2 handshake. That’s why anything happening on the actual VPN wire (the key exchange in Parts 4–5, and the authentication coming up in Part 8) is strongSwan’s job. Two tools, two jobs.

A tiny throwaway container gives us a clean 3.5+ environment in seconds:

```
docker run --rm -it alpine:3.22 sh
```

Then inside you install openssl:

```
apk add --no-cache openssl
openssl version          # expect OpenSSL 3.5.x or newer
mkdir -p /pqauth && cd /pqauth
```

All commands below run in that same shell.

## Step 1: Confirm the PQC algorithms are there

```
openssl list -signature-algorithms | grep -iE "ml-dsa|slh-dsa|ed25519"
```

You’ll see the ML-DSA and SLH-DSA families listed, for example:

```
{ 1.3.101.112, ED25519 } @ default
{ 2.16.840.1.101.3.4.3.18, id-ml-dsa-65, ML-DSA-65, MLDSA65 } @ default
{ 2.16.840.1.101.3.4.3.20, id-slh-dsa-sha2-128s, SLH-DSA-SHA2-128s } @ default
...
```

There they are: straight from OpenSSL’s default provider, no plugins required.

## Step 2: Mint a self-signed cert

We’ll generate a key *and* a self-signed certificate for eight algorithms in one loop. Quick note on the yardstick: a **self-signed certificate** (subject = issuer, signed with its own key) is the cleanest possible comparison: it bundles exactly one public key and one signature with no external CA, so the size reflects *only* the algorithm’s footprint. We also output **DER** (the compact binary encoding that actually travels on the wire), not the base64 PEM text you usually see.

```
algs="ED25519:ed25519 EC:ecp256 RSA:rsa3072 ML-DSA-44:mldsa44 ML-DSA-65:mldsa65 ML-DSA-87:mldsa87 SLH-DSA-SHA2-128s:slh128s SLH-DSA-SHA2-128f:slh128f"

for entry in $algs; do
    alg=${entry%%:*}; name=${entry##*:}
    case "$alg" in
        RSA) openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:3072 -out "$name.key" ;;
        EC)  openssl genpkey -algorithm EC  -pkeyopt ec_paramgen_curve:P-256 -out "$name.key" ;;
        *)   openssl genpkey -algorithm "$alg" -out "$name.key" ;;
    esac
    openssl req -x509 -new -key "$name.key" -out "$name.crt" -days 365 -subj "/CN=$name-peer" -outform DER
done
```

Notice that for the post-quantum algorithms, generating a key uses the *exact same one-liner* as a classical one; only the algorithm name changes. That’s the beauty of OpenSSL 3.5: post-quantum isn’t a separate, special workflow, it’s just another algorithm you pass to the same command.

## Step 3: Weigh them

```
ls -la *.crt | awk '{print $5"  "$9}' | sort -n
```

Brace yourself:

```
326    ed25519.crt
389    ecp256.crt
1043   rsa3072.crt
3987   mldsa44.crt
5516   mldsa65.crt
7474   mldsa87.crt
8139   slh128s.crt
17371  slh128f.crt
```

Look at that jump! Let’s put it in plain terms with the underlying numbers:

| Algorithm | Security | Public key | Signature | Cert (DER) |
|---|---|---|---|---|
| Ed25519 | ~128-bit | 32 B | 64 B | 326 B |
| ECDSA P-256 | ~128-bit | 65 B | ~70 B | 389 B |
| RSA-3072 | ~128-bit | 384 B | 384 B | 1043 B |
ML-DSA-44 |
NIST L2 | 1312 B | 2420 B | 3987 B |
ML-DSA-65 |
NIST L3 | 1952 B | 3309 B | 5516 B |
ML-DSA-87 |
NIST L5 | 2592 B | 4627 B | 7474 B |
| SLH-DSA-128s | NIST L1 | 32 B | 7856 B | 8139 B |
| SLH-DSA-128f | NIST L1 | 32 B | 17088 B | 17371 B |

An Ed25519 certificate is just **326 bytes**. The equivalent ML-DSA-65 cert is **5516 bytes**, roughly **17× larger**. And SLH-DSA-128f? Its signature *alone* is **17088 bytes**, bigger than many entire web pages. Notice SLH-DSA’s quirk too: a tiny 32-byte public key, but a gigantic signature. *Whoa.*

**Wait, why isn’t the cert just key + signature?** Good catch. For ML-DSA-65, key (1952 B) + signature (3309 B) = 5261 B, yet the cert is 5516 B, about 255 B more. A certificate isn’t a simple concatenation; it’s a structured X.509 document embedding the key and signature alongside metadata (version, serial, validity dates, issuer/subject names, algorithm OIDs, a few extensions, plus ASN.1/DER framing on every field). That overhead is roughly *fixed* (~230–260 B) regardless of algorithm, which is why it dominates a tiny Ed25519 cert but barely registers on a chunky ML-DSA one.

## Why this size story matters

Handshakes don’t carry one cert; they carry **certificate chains plus a handshake signature**. Swap a 3-cert ECDSA chain (~1.2 KB total) for an ML-DSA-65 chain and you’re suddenly shipping

**15–20 KB** in the handshake. In IKEv2 that means the

`IKE_AUTH`

exchange balloons and leans hard on **fragmentation**: the same pressure ML-KEM put on

`IKE_INTERMEDIATE`

back in the key-exchange pillar, but now on the authentication leg.And “leaning hard on fragmentation” isn’t just a minor detail. Fragmentation was designed as an occasional fallback for the rare oversized message; post-quantum flips that so *almost every* handshake is large and fragmented. That brings real costs:

**Packet loss hurts more.** An N-fragment message reassembles only if*all N*arrive; lose one and the whole thing retransmits. On lossy links, big PQC handshakes retransmit more and latency spikes.**Middleboxes are hostile to fragments.** Firewalls, NATs, and load balancers routinely drop or mishandle fragmented UDP, producing handshakes that fail in really hard-to-debug ways.**Bigger pre-auth attack surface.** These bytes fly*before*the peer has proven who it is, so a responder must buffer and reassemble fragments from a not-yet-authenticated initiator: extra state an attacker can try to overload.

None of this *breaks* PQC auth (strongSwan handles it), but it’s why “the certs got bigger” turns into round trips, retransmits, and middlebox trouble at scale. We’ll watch exactly this happen on the wire in Part 8.

## Step 4: Are they slow?

A common worry, so before we sign anything ourselves (we’ll do that in Step 5), here’s what to expect per signature:

| Algorithm | Signing | Notes |
|---|---|---|
| Ed25519 / ECDSA | ~instant | so fast the timing is dominated by just launching the command |
| ML-DSA-44/65/87 | ~instant | sits right alongside the classics |
| SLH-DSA-128f | a few ms | noticeably heavier |
| SLH-DSA-128s | ~100+ ms | dramatically slower to sign |

The takeaway: **ML-DSA signs and verifies right alongside Ed25519 and ECDSA**, while **SLH-DSA’s s (“small”) variants are hugely slower.** That slowness is

*exactly*why SLH-DSA is reserved for things you sign rarely (a root CA signs a handful of certs a year, so who cares if each takes 100 ms?) rather than a busy TLS terminator doing thousands of handshakes a second. You’ll feel that difference for yourself with the

`time`

command in Step 5.## Step 5: Sign, verify, tamper

Certificates are about *trust*, and trust comes from signatures that can’t be forged. Let’s prove it.

```
echo "transfer 1000 to tom" > msg.txt
openssl pkeyutl -sign -inkey mldsa65.key -rawin -in msg.txt -out msg.sig
wc -c < msg.sig          # ~3309 bytes

# Pull out the public half and verify with it
openssl pkey -in mldsa65.key -pubout -out mldsa65.pub
openssl pkeyutl -verify -pubin -inkey mldsa65.pub -rawin -in msg.txt -sigfile msg.sig
```

You should see `Signature Verified Successfully`

. Note the clean split: signing used the **private** key, verifying used *only* the **public** key. That’s the whole point of public-key signatures: signing is private, verifying is public.

Now tamper with the message and watch it fail:

```
echo "transfer 1000 to mallory" > msg.txt
openssl pkeyutl -verify -pubin -inkey mldsa65.pub -rawin -in msg.txt -sigfile msg.sig
```

`Signature Verification Failure`

. Change a single byte and the post-quantum signature rejects it: exactly the unforgeable integrity guarantee we depend on, now resting on lattice math instead of elliptic curves.

Want to *feel* the SLH-DSA difference? Time it:

```
time openssl pkeyutl -sign -inkey slh128s.key -rawin -in msg.txt -out slh.sig
wc -c < slh.sig          # ~7856 bytes
```

Noticeably slower, and the signature more than twice the ML-DSA size. That’s the hash-based trade-off in action: ultra-cautious security, but you’d never want it on a high-volume handshake.

## What we just did

You generated post-quantum certificates, measured the size hit with your own hands, watched ML-DSA and SLH-DSA sign and verify (and reject a forgery), and felt the SLH-DSA speed penalty. The “certs get bigger” story is no longer something you read; it’s something you weighed.

But certificates sitting in a folder are only half the fun. In **Part 8** we hand these certs to strongSwan and use them to mutually authenticate a *real* IKEv2 VPN tunnel: classical ECDSA first, then bleeding-edge post-quantum ML-DSA. We’ll watch the handshake balloon and fragment exactly as predicted, and stand right at the bleeding edge where this stuff is still being built. See you there!
