Post-Quantum Cryptography: A Practical Migration Guide
Here's the deal: post-quantum cryptography is no longer a "future problem." NIST finalized its first three post-quantum standards in August 2024 (FIPS 203, FIPS 204, FIPS 205), and the conversation in r/programming this week has shifted from "should we care" to "how do we actually do this." If you're running systems that handle key exchange, digital signatures, or long-lived encrypted data, the migration window is open right now. Not next year.
I've been through enough cryptographic migrations to know that most guides hand you a list of algorithm names and call it a day. This one won't. We're going to talk about the decisions that actually matter, the tradeoffs engineers skip over, and what the code looks like in practice.
The Three Algorithms You Actually Need to Know
NIST standardized three algorithms. Get comfortable with what each one does before you touch a config file.
ML-KEM (FIPS 203) — formerly CRYSTALS-Kyber. This is your key encapsulation mechanism. It replaces ECDH/RSA for key exchange. Think TLS handshakes, encrypted messaging, session establishment.
ML-DSA (FIPS 204) — formerly CRYSTALS-Dilithium. This is your primary signature scheme. It replaces ECDSA and RSA signatures. Think code signing, JWT signing, certificate authorities, blockchain transaction signing.
SLH-DSA (FIPS 205) — formerly SPHINCS+. Stateless hash-based signatures. Slower and larger than ML-DSA, but its security assumptions are more conservative. Use it for high-assurance contexts where you're willing to pay the size penalty.
There's a fourth in progress: ML-DSA's counterpart for situations requiring smaller signatures. But for now, these three are what you build on.
Infrastructure: Where to Start the Migration
The path of least regret is to start at the edges of your infrastructure, not the core.
TLS termination first. Your load balancers and API gateways are the highest-leverage starting point. Migrating TLS to hybrid mode (more on that in a moment) protects all traffic without touching application code. If you're on nginx or a cloud provider's managed TLS, check their current support status. AWS Certificate Manager and Cloudflare have both announced or shipped hybrid PQC support in 2025-2026.
Certificate signing second. Your internal PKI, code signing pipelines, and any long-lived certificates are next. A certificate signed today with ECDSA that expires in 2030 is a problem if a cryptographically relevant quantum computer arrives before then. The "harvest now, decrypt later" attack is real: adversaries are storing encrypted traffic today to decrypt it when the hardware exists.
Application-layer key exchange last. Database encryption at rest, end-to-end encrypted messaging, and blockchain key management are the hardest to migrate and require the most careful testing. Don't start here.
Blockchain and Web3: The Signature Problem Is Bigger Here
If you're running or building on a blockchain system, the signature migration is more complex than it is for web infrastructure. Every wallet, every transaction, every smart contract interaction is signed with ECDSA (secp256k1 on Bitcoin and Ethereum). Migrating that at the protocol level requires consensus across the entire network.
For application-layer signing (off-chain messages, API authentication, oracle signatures), you can migrate independently. For on-chain transaction signing, you're waiting on protocol-level changes. Ethereum's EIP process has active proposals for quantum-resistant accounts, and Ethereum Foundation researchers have discussed account abstraction as a migration path.
What you can do right now in Web3 contexts:
- Use ML-DSA for any off-chain signature needs (API authentication, signed metadata, oracle payloads)
- Generate and store quantum-resistant keypairs for wallets you control, even if the network can't use them yet
- Audit any smart contract that relies on signature verification schemes that are application-defined rather than protocol-defined
Hybrid Mode: The Part Most Guides Skip Over
Here's what most migration guides miss: you should not rip out classical cryptography and replace it with post-quantum algorithms directly. Run them in parallel, in hybrid mode, during the transition period.
The reason is straightforward. Post-quantum algorithms are newer. Their implementations have had less time to be battle-tested. A bug in a new ML-KEM implementation is a real risk. Hybrid mode means an attacker has to break both the classical algorithm AND the post-quantum algorithm simultaneously. That's a much harder problem.
In practice, hybrid key exchange looks like this in TLS 1.3: the client and server negotiate a shared secret using both X25519 (classical) and ML-KEM-768 (post-quantum), then combine the outputs using HKDF. The IETF draft for hybrid key exchange documents the exact construction.
The OpenSSL 3.x series with the OQS provider (from the Open Quantum Safe project) supports hybrid groups. Here's what enabling a hybrid ciphergroup looks like in an OpenSSL TLS server configuration:
openssl s_server \
-cert server.crt \
-key server.key \
-groups X25519MLKEM768 \
-tls1_3
And on the client side, verifying the negotiated group:
openssl s_client \
-connect localhost:4433 \
-groups X25519MLKEM768 \
-tls1_3 \
2>&1 | grep "Server Temp Key"
The OQS provider is available as a loadable module for OpenSSL 3.x and supports all three NIST-finalized algorithms. If you're on a platform using BoringSSL (Chrome, many Google services), hybrid X25519+Kyber has been shipping in production since 2023.
Signing with ML-DSA: A Real Example
For signature migration, the liboqs library is the most practical starting point for C/C++ systems. For Python, the oqs-python wrapper is usable in production contexts.
Here's a working key generation and signing example using oqs-python:
import oqs
# ML-DSA-65 corresponds to FIPS 204 security level 3
sig_alg = "ML-DSA-65"
with oqs.Signature(sig_alg) as signer:
public_key = signer.generate_keypair()
message = b"transaction payload or document to sign"
signature = signer.sign(message)
print(f"Public key size: {len(public_key)} bytes")
print(f"Signature size: {len(signature)} bytes")
with oqs.Signature(sig_alg) as verifier:
is_valid = verifier.verify(message, signature, public_key)
print(f"Signature valid: {is_valid}")
Output you'll see:
Public key size: 1952 bytes
Signature size: 3309 bytes
Signature valid: True
Compare that to a P-256 ECDSA signature: 64 bytes for the signature, 64 bytes for the public key. ML-DSA-65 is significantly larger. That's not a dealbreaker, but it's a real consideration for systems with tight packet size budgets, blockchain transaction formats, or high-volume signing pipelines.
If signature size is a hard constraint, SLH-DSA-SHA2-128s gives you smaller signatures (7,856 bytes) at the cost of slower signing. ML-DSA-44 (security level 2) reduces the signature to 2,420 bytes if you can accept a lower security margin. Know your threat model before optimizing.
The AI/ML Connection Worth Paying Attention To
This is slightly tangential but worth flagging: AI systems that store and process sensitive training data, model weights, and inference outputs are subject to the same "harvest now, decrypt later" risk as any other encrypted data. If you're encrypting model checkpoints, fine-tuned weights, or proprietary datasets at rest with RSA or ECC today, that encrypted data could be decrypted in the future.
The practical implication is that AI infrastructure teams should treat their encryption-at-rest strategy with the same urgency as web infrastructure teams. Object storage encryption (S3, GCS, Azure Blob), key management services, and any inter-service communication carrying model data are all in scope.
The Tradeoffs You Need to Budget For
Let me be direct about the costs, because you'll hit them in planning conversations:
Size. ML-KEM-768 public keys are 1,184 bytes. ML-DSA-65 signatures are 3,309 bytes. If you're storing millions of signatures in a database or embedding them in QR codes or blockchain transactions, this matters. Audit your storage and bandwidth assumptions.
Performance. ML-KEM is fast. On a modern server CPU, ML-KEM-768 key generation and encapsulation are faster than RSA-2048. ML-DSA signing is slower than Ed25519 but not dramatically so for most use cases. SLH-DSA signing is slow enough that you'll notice it in high-throughput pipelines.
Library maturity. OpenSSL's native PQC support is still maturing. The OQS provider works, but it's not the same as code that's been in production for fifteen years. Run hybrid mode. Test thoroughly. Don't skip the integration tests.
Key management complexity. Hybrid keypairs mean you're managing more key material. Your key rotation, backup, and recovery procedures need to account for this.
Concrete Recommendation
Don't wait for your platform team to hand you a migration plan. Start with TLS hybrid mode at your load balancer layer this quarter. It's the lowest-risk, highest-coverage move available right now, and most major cloud providers either support it or have it on their near-term roadmap.
After that, audit any signature scheme you control at the application layer. If you're signing JWTs, API payloads, code artifacts, or blockchain-adjacent data, move those to ML-DSA-65 with a classical fallback during the transition.
Skip the "wait and see" posture. The NIST standards are finalized. The libraries exist. The threat of encrypted traffic being harvested today and decrypted later is real and documented. The migration is not a single sprint, but the first step is a weekend of work. Do it now while it's optional, not in two years when it's urgent.
FIPS 203, 204, and 205 are available directly from NIST's post-quantum cryptography project page. The Open Quantum Safe project maintains the most complete open-source implementation suite at openquantumsafe.org.