1. The exact problem this technology solves (real attack model)
Crypto discovery is the prerequisite for everything else in a PQ migration. You cannot replace what you have not catalogued. The threat model is harvest-now-decrypt-later (HNDL): an adversary captures TLS sessions, IPsec tunnels, encrypted email, signed software artifacts, or VPN traffic today and stores the ciphertext against the day a cryptographically-relevant quantum computer (CRQC) can run Shor against the key-establishment material. RSA-2048, RSA-3072, ECDSA-P256, X25519, DH-2048 — all break under Shor. AES-256-GCM data keys are safe (Grover gives a quadratic speed-up, not exponential), but the session keys that wrap them are typically derived from an ECDHE handshake and are therefore harvestable.
The discovery problem in a regulated Irish firm — bank, insurer, payments processor, utility — is that nobody has a complete inventory. A 2024 Ponemon study put the average enterprise figure at 61 % of cryptographic assets unknown to the security team. Typical gaps:
- TLS terminators on internal microservices behind a service mesh, using
mTLSwith certificates issued by an internal CA whose root is on a USB stick in a safe in Sandyford. - SSH host keys (
ssh-rsa,ssh-ed25519) on every Linux box, every network appliance, every jump host, generated at provisioning time and never rotated. - Signed artifacts: container images signed with
cosignusing ECDSA, JAR files signed withjarsigneragainst an RSA-2048 keystore, RPM/DEB packages signed with GPG, firmware images signed with vendor RSA keys. - Long-lived keys: HSM-resident RSA wrapping keys, KMIP master keys, database TDE keys, S/MIME identities for board correspondence, code-signing certs valid through 2031.
- DNSSEC zone-signing keys (
ZSK) and key-signing keys (KSK) — almost universallyRSASHA256orECDSAP256SHA256.
Without this inventory, you cannot build a migration plan, prioritise by exposure window, or attest to the Central Bank that you have a defensible PQ roadmap under the DORA Article 9 cryptography control.
2. The cryptographic primitive in technical detail
Discovery is not itself a cryptographic operation, but you are inventorying primitives, so you must classify each finding by what replaces it.
Key establishment — anything performing Diffie-Hellman (TLS ECDHE, IKEv2 DH groups, SSH KEX, Signal X3DH) is replaced by ML-KEM (FIPS 203, formerly Kyber). ML-KEM is IND-CCA2 secure under the Module-LWE assumption. The three parameter sets — ML-KEM-512, ML-KEM-768, ML-KEM-1024 — target NIST levels 1, 3, 5. Public keys are 800/1184/1568 bytes; ciphertexts 768/1088/1568 bytes. In practice you deploy hybrid: X25519MLKEM768 (codepoint 0x11EC) per draft-kwiatkowski-tls-ecdhe-mlkem and now widely adopted.
Digital signatures — RSA-PSS, RSA-PKCS#1v1.5, ECDSA, Ed25519 are replaced by ML-DSA (FIPS 204, formerly Dilithium) for general use, and SLH-DSA (FIPS 205, formerly SPHINCS+) for cases where you cannot tolerate a lattice assumption — typically root CAs and firmware signing with decade-plus validity. Both are EUF-CMA secure. ML-DSA-65 has 1952-byte public keys and 3309-byte signatures; SLH-DSA-SHA2-128s has 32-byte public keys but 7856-byte signatures and very slow signing. Choose accordingly.
Symmetric and hashes — AES-256-GCM, ChaCha20-Poly1305, SHA-256, SHA-3 stay. Flag anything weaker (AES-128 in long-retention contexts, SHA-1 anywhere) for replacement before PQ migration, not after.
3. Reference implementations in 2026
- liboqs 0.12 (Open Quantum Safe) — reference implementations of all FIPS 203/204/205 parameter sets, plus the older NIST round-3 candidates for backwards interop testing. Production-grade for ML-KEM and ML-DSA; SLH-DSA is correct but slow.
- OpenSSL 3.5 ships
ML-KEMandML-DSAnatively; the oqs-provider remains useful for hybrid groups not yet upstreamed and for SLH-DSA. - BoringSSL — Google enabled
X25519MLKEM768in Chrome stable from version 131 (late 2024). Server-side support across Cloudflare, Google Front End, and a growing share of CDNs. - OpenSSH 9.9+ —
mlkem768x25519-sha256KEX is the default in 10.0 (April 2025). SSH host keys remain Ed25519/RSA pending FIPS 204 finalisation in OpenSSH itself; expectssh-mldsa65in 10.x during 2026. - BIND 9.20 — experimental ML-DSA DNSSEC algorithm support; not yet IETF-allocated codepoints, so do not deploy to production zones. Watch draft-ietf-dnsop-dnssec-pqc.
- AWS KMS —
pq-hybridmode on the KMS TLS endpoint since 2020 (initially Kyber-512, now ML-KEM-768). KMS-resident signing keys are still classical. - GCP Cloud KMS / Azure Key Vault — TLS to the control plane is hybridised; customer-managed PQ signing keys are in preview.
- Cloudflare — PQ TLS on by default for any origin that negotiates it; about 38 % of human Cloudflare traffic was PQ-protected by end of 2025.
4. The deployment pattern
A discovery sweep has four passes: network, filesystem, certificate stores, and signed-artifact registries. The output is a single normalised inventory keyed by SHA-256 of the public key, with primitive, parameter, location, expiry, and exposure window. Example skeleton (Python, pseudocode):
import ssl, socket, hashlib, json
from cryptography import x509
from cryptography.hazmat.primitives import serialization
def probe_tls(host, port=443):
ctx = ssl.create_default_context()
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
with socket.create_connection((host, port), timeout=5) as s:
with ctx.wrap_socket(s, server_hostname=host) as tls:
der = tls.getpeercert(binary_form=True)
cipher = tls.cipher() # (suite, proto, bits)
group = tls.group() # 3.12+: e.g. 'X25519MLKEM768'
cert = x509.load_der_x509_certificate(der)
pub = cert.public_key()
spki = pub.public_bytes(
serialization.Encoding.DER,
serialization.PublicFormat.SubjectPublicKeyInfo)
return {
"host": host,
"tls_version": cipher[1],
"kex_group": group,
"sig_alg": cert.signature_algorithm_oid.dotted_string,
"spki_sha256": hashlib.sha256(spki).hexdigest(),
"not_after": cert.not_valid_after_utc.isoformat(),
"pq_ready": group and "MLKEM" in group,
}
# Feed it from your asset inventory, not a /16 sweep.
for host in load_asset_inventory():
try: print(json.dumps(probe_tls(host)))
except Exception as e: log_failure(host, e)
Then run parallel passes: find / -name 'id_*' -o -name '*.pem' -o -name '*.p12' across managed estate via Ansible; cosign tree against every OCI registry; gpg --list-keys --with-colons on signing hosts; dig +dnssec against every zone; KMIP Locate calls against every HSM partition.
5. What goes wrong in production
- Middlebox intolerance. ClientHello with the
X25519MLKEM768group exceeds the 1500-byte single-packet threshold. Older F5, Palo Alto, and Cisco gear silently drops the second TLS record. You discover this only when 2 % ofSchedule a sovereign-cryptography assessment
Book a security call →