Part 9 made the key exchange quantum-safe. ML-KEM-768 is running on all three routers, fragmentation is handling the bigger messages, and the phased rollout lets you migrate at your own pace. But authentication is still a pre-shared key, and at scale that doesn’t work.
This post closes the gap. We swap PSK for ML-DSA certificates (FIPS 204), the same signature scheme from Parts 6-8 in the container labs. The difference: on IOS XE, the path from “I have a key” to “I have a certificate” requires some work. And once the tunnel is up, we measure exactly what ML-DSA costs on the wire.
The PKI lives on your workstation #
The entire ML-DSA identity for each router is built on your workstation with OpenSSL 3.5+ and imported as a PKCS#12 bundle (a single password-encrypted file carrying the private key, its certificate, and the issuing CA chain together).
This is the verified path for getting ML-DSA identities onto the routers. We generate a root CA per parameter set, an identity certificate for R1, R2, and R3, and a PKCS#12 bundle for each.
=== Artifact sizes in bytes (R1 identity, DER-encoded cert)
ALGORITHM PUBKEY PRIVKEY CERT P12
rsa-2048 294 1218 920 3698
ecdsa-p256 91 138 528 1827
mldsa44 1334 2626 4118 11504
mldsa65 1974 4098 5647 16032
mldsa87 2614 4962 7605 20816
Look at that table: a ML-DSA-65 public key is 1,974 bytes against 294 for RSA-2048 and against 91 for ECDSA P-256. The certificate carrying it is 5,647 bytes against 920. That’s not a small change, it’s an order of magnitude, and IKEv2 has to carry it.
Import the bundle #
Copy the PKCS#12 file to the router and import:
R1(config)# crypto pki import TP-MLDSA65 pkcs12 bootflash:/mldsa65-r1.p12 password cisco123
% Importing pkcs12...
Source filename [mldsa65-r1.p12]?
Reading file from bootflash:/mldsa65-r1.p12
CRYPTO_PKI: Imported PKCS12 file successfully.
That password cisco123 protects the bundle for the few seconds it sits on bootflash: and nothing else. The bundle gets deleted right after import. Don’t reuse this pattern for a bundle that lives anywhere longer than one import. Delete it immediately:
R1# delete /force bootflash:/mldsa65-r1.p12
One more thing: the default revocation-check crl on the trustpoint means the router tries to download a CRL (Cert Revocation List) before trusting a peer’s certificate. Lab certificates have no CRL distribution point, so the handshake stalls. Fix it:
R1(config)# crypto pki trustpoint TP-MLDSA65
R1(ca-trustpoint)# revocation-check none
Confirm the router parsed an ML-DSA certificate:
R1# show crypto pki certificates verbose TP-MLDSA65
Certificate
Status: Available
Version: 3
Certificate Usage: Signature
Issuer:
cn=PQC-LAB-ROOT-mldsa65
Subject:
Name: R1-mldsa65
IP Address: 10.0.12.1
cn=R1-mldsa65
Subject Key Info:
Public Key Algorithm: ML-DSA
Public Key Size: (1974 bytes)
Signature Algorithm: ML-DSA-65
X509v3 extensions:
X509v3 Subject Alternative Name:
IP Address : 10.0.12.1
Extended Key Usage:
Server Auth
Client Auth
Public Key Algorithm: ML-DSA, 1,974 bytes, matching the container lab OpenSSL output exactly.
Both Server Auth and Client Auth EKUs are needed. An IKEv2 peer is initiator on one negotiation and responder on the next, so a client-only certificate fails half the time.
Swap PSK for ML-DSA #
Do this on both R1 and R3. The key exchange config doesn’t change at all: pqc mlkem768 optional from Part 9 stays exactly as it is. Only the authentication method changes.
crypto ikev2 profile MLDSA-PROFILE
match identity remote address 10.0.23.2 255.255.255.255
identity local address 10.0.12.1
authentication local mldsa-sig
authentication remote mldsa-sig
pki trustpoint TP-MLDSA65
dpd 30 5 periodic
crypto ipsec profile MLDSA-IPSEC
set transform-set CLASSICAL-TS
set ikev2-profile MLDSA-PROFILE
interface Tunnel0
tunnel protection ipsec profile MLDSA-IPSEC
The router warns you:
Warning: MLDSA Auth packets are extremely large. Please enable IKEv2 Fragmentation.
Fragmentation was already enabled in Part 9, so we’re covered. Changing tunnel protection shuts the interface, so bring it back:
R1(config)# interface Tunnel0
R1(config-if)# no shutdown
The payoff #
R1# ping 192.168.100.2
!!!!!
Success rate is 100 percent (5/5), round-trip min/avg/max = 1/1/4 ms
R1# show crypto ikev2 sa detailed
Tunnel-id Local Remote fvrf/ivrf Status
2 10.0.12.1/500 10.0.23.2/500 none/none READY
Encr: AES-CBC, keysize: 256, PRF: SHA512, Hash: SHA512, DH Grp:20, Auth sign: MLDSA, Auth verify: MLDSA
PQC Key Exchange: ML-KEM-768
...
IETF Std Fragmentation enabled.
Quantum-safe Encryption using PQC: ML-KEM-768
IETF Std Fragmentation MTU in use: 1372 bytes.
There it is, on one line: Auth sign: MLDSA, Auth verify: MLDSA next to PQC Key Exchange: ML-KEM-768. Both halves of the tunnel are quantum-safe. Key exchange resists harvest-now-decrypt-later. Authentication resists a future forged identity.
What ML-DSA actually costs on the wire #
The warning said the auth packets are “exetremely large”, but how large? We set up a packet capture on R2 (the transit hop that sees every IKE packet without being a crypto endpoint) and measured one complete handshake for each signature algorithm.
| Authentication | IKE_AUTH frames | IKE_AUTH bytes | Whole handshake | vs RSA-2048 |
|---|---|---|---|---|
| RSA-2048 | 3 | 3,462 | 7 frames / 7,111 B | 1.0x |
| ML-DSA-44 | 12 | 15,384 | 16 frames / 19,033 B | 4.4x |
| ML-DSA-65 | 16 | 20,752 | 20 frames / 24,401 B | 6.0x |
| ML-DSA-87 | 20 | 27,848 | 24 frames / 31,497 B | 8.0x |
IKE_SA_INIT is 2 frames and about 1.1 KB in every case: it only carries the classical ECDH exchange (group 20), whose public value is 512 bytes. The ML-KEM IKE_INTERMEDIATE exchange is also constant at 2 frames and 2,548 bytes (the ML-KEM-768 encapsulation key alone is 1,184 bytes). Neither changes with the signature algorithm. All the variable growth is in IKE_AUTH, which is where the certificate and the signature travel.
The largest ML-DSA frame is 1,422 bytes in every run. That’s the fragmentation MTU doing its job: instead of one enormous datagram you get a dozen or more well-behaved ones. Exactly why the router nags you about fragmentation.
Choosing a parameter set. ML-DSA-65 costs 35% more IKE_AUTH bytes than ML-DSA-44 and buys you NIST security category 3 instead of 2. ML-DSA-87 costs another 34% on top for category 5. Unless you have a specific mandate, 65 is the sensible default.
Is 20 KB a problem? For a site-to-site tunnel that negotiates once and rekeys every 24 hours, no. For a hub terminating thousands of DMVPN spokes that all reconnect after a power event, definitely something to factor into capacity planning. The cost is per-negotiation, not per-packet: once the SA is up, ESP data plane traffic is unchanged.
Where we stand now #
The tunnel is fully quantum-safe. But we just proved it on a single pair of routers. How do you migrate authentication across a whole network? That’s a different problem from key exchange, and the answer is less comfortable. Key exchange negotiates with optional; authentication has no fallback keyword. Part 11 walks through the migration playbook, the experiments that show where it breaks, and the operational gotchas that only show up on real hardware.
See you there. Bring your planning hat.