Post-Quantum Cryptography Explained
CRYSTALS-Kyber · CRYSTALS-Dilithium · SPHINCS+ · .NET Integration
Finalized 2024
Kyber-512 / 768 / 1024
Est. 2030–2040
Kyber standard
Introduction: Why Post-Quantum Cryptography?
Classical public-key algorithms — RSA, ECDH, ECDSA — rely on mathematical problems (integer factorization, discrete logarithm) that are computationally hard for classical computers but solvable in polynomial time by a sufficiently large quantum computer using Shor's algorithm. When quantum computers reach sufficient scale (sometimes called "Q-Day"), all asymmetric encryption and digital signatures based on these problems will be broken.
Post-Quantum Cryptography (PQC) refers to cryptographic algorithms believed to be secure against both classical and quantum computers. In August 2024, NIST finalized three post-quantum standards: FIPS 203 (ML-KEM / Kyber), FIPS 204 (ML-DSA / Dilithium), and FIPS 205 (SLH-DSA / SPHINCS+).
🔑 NIST PQC Finalized Algorithms at a Glance
| Algorithm | FIPS | Type | Math Basis | Public Key | Use Case |
|---|---|---|---|---|---|
| ML-KEM (Kyber-768) | FIPS 203 | KEM | Module-LWE | 1184 bytes | Key Exchange / TLS |
| ML-DSA (Dilithium3) | FIPS 204 | Signature | Module-LWE/SIS | 1952 bytes | Code/Doc Signing |
| SLH-DSA (SPHINCS+) | FIPS 205 | Signature | Hash-based | 32–64 bytes | Long-term signing |
| RSA-2048 (classical) | — | KEM+Sig | Integer Factoring | 256 bytes | ❌ Quantum-vulnerable |
CRYSTALS-Kyber (ML-KEM)
Kyber is a Key Encapsulation Mechanism (KEM) based on the hardness of the Module Learning With Errors (MLWE) problem. It does not encrypt data directly — instead, it establishes a shared secret between two parties, which is then used as a symmetric key (e.g., for AES). Kyber comes in three security levels:
- Kyber-512 — AES-128 equivalent security (512-bit module dimension)
- Kyber-768 — AES-192 equivalent, NIST recommended for most applications
- Kyber-1024 — AES-256 equivalent, for highest security requirements
// Install: dotnet add package BouncyCastle.Cryptography
using Org.BouncyCastle.Pqc.Crypto.Crystals.Kyber;
using Org.BouncyCastle.Security;
// Key Generation (receiver side)
var random = new SecureRandom();
var keyGenParams = new KyberKeyGenerationParameters(random, KyberParameters.kyber768);
var keyGen = new KyberKeyPairGenerator();
keyGen.Init(keyGenParams);
var keyPair = keyGen.GenerateKeyPair();
var publicKey = (KyberPublicKeyParameters)keyPair.Public;
var privateKey = (KyberPrivateKeyParameters)keyPair.Private;
// Encapsulation (sender side — generates ciphertext + shared secret)
var encapsulator = new KyberKemGenerator(random);
var secretWithEncapsulation = encapsulator.GenerateEncapsulated(publicKey);
byte[] ciphertext = secretWithEncapsulation.GetEncapsulation();
byte[] sharedSecretA = secretWithEncapsulation.GetSecret(); // 32 bytes
// Decapsulation (receiver side)
var decapsulator = new KyberKemExtractor(privateKey);
byte[] sharedSecretB = decapsulator.ExtractSecret(ciphertext);
// Both parties now hold identical shared secrets
Console.WriteLine($"Match: {sharedSecretA.SequenceEqual(sharedSecretB)}"); // True
// Use sharedSecret as AES key for actual data encryption
using var aes = Aes.Create();
aes.Key = sharedSecretA; // 256-bit AES key
CRYSTALS-Dilithium (ML-DSA)
Dilithium is a digital signature scheme based on the hardness of Module-LWE and Module-SIS problems. It produces signatures that are much larger than RSA or ECDSA but are considered secure against quantum attacks. FIPS 204 specifies three parameter sets: ML-DSA-44, ML-DSA-65, and ML-DSA-87.
using Org.BouncyCastle.Pqc.Crypto.Crystals.Dilithium;
using Org.BouncyCastle.Security;
var random = new SecureRandom();
// Generate Dilithium3 key pair (ML-DSA-65 equivalent)
var keyGenParams = new DilithiumKeyGenerationParameters(
random, DilithiumParameters.Dilithium3);
var keyGen = new DilithiumKeyPairGenerator();
keyGen.Init(keyGenParams);
var keyPair = keyGen.GenerateKeyPair();
byte[] message = System.Text.Encoding.UTF8.GetBytes("Sign this document");
// Sign
var signer = new DilithiumSigner();
signer.Init(true, keyPair.Private);
signer.BlockUpdate(message, 0, message.Length);
byte[] signature = signer.GenerateSignature();
// Signature size for Dilithium3: ~3,293 bytes (vs ~256 for ECDSA P-256)
Console.WriteLine($"Sig size: {signature.Length} bytes");
// Verify
var verifier = new DilithiumSigner();
verifier.Init(false, keyPair.Public);
verifier.BlockUpdate(message, 0, message.Length);
bool isValid = verifier.VerifySignature(signature);
Console.WriteLine($"Valid: {isValid}"); // True
📊 Key & Signature Size Comparison
SPHINCS+ (SLH-DSA)
SPHINCS+ is a stateless hash-based signature scheme. Unlike lattice-based schemes (Kyber, Dilithium), SPHINCS+ relies only on the security of hash functions — making it conservative and extremely well-understood. Its main tradeoff is larger signature sizes (8–50 KB depending on parameter set) and slower signing, but it offers excellent security guarantees and doesn't require any new mathematical assumptions.
using Org.BouncyCastle.Pqc.Crypto.SphincsPlus;
var random = new SecureRandom();
var keyParams = new SphincsPlusKeyGenerationParameters(
random, SphincsPlusParameters.sha2_128f); // fast variant
var keyGen = new SphincsPlusKeyPairGenerator();
keyGen.Init(keyParams);
var keyPair = keyGen.GenerateKeyPair();
byte[] data = System.Text.Encoding.UTF8.GetBytes("SPHINCS+ test document");
var signer = new SphincsPlusSigner();
signer.Init(true, keyPair.Private);
signer.BlockUpdate(data, 0, data.Length);
byte[] sig = signer.GenerateSignature();
Console.WriteLine($"SPHINCS+ sig size: {sig.Length} bytes"); // ~17,088 bytes for sha2_128f
.NET 9 Native PQC Support
Starting with .NET 9 (released Nov 2024), Microsoft added native post-quantum algorithm support in System.Security.Cryptography. ML-KEM (Kyber) is available without BouncyCastle as of .NET 9 Preview builds, with ML-DSA support planned for .NET 10.
// .NET 9+ — System.Security.Cryptography.MLKem
using System.Security.Cryptography;
// Generate key pair (ML-KEM-768)
using var kem = MLKem.GenerateKey(MLKemAlgorithm.MLKem768);
byte[] publicKeyBytes = kem.ExportPublicKey();
// Encapsulate (sender)
MLKem.TryDecapsulate(publicKeyBytes, out byte[] ciphertext, out byte[] sharedSecretEnc);
// Decapsulate (receiver)
byte[] sharedSecretDec = kem.Decapsulate(ciphertext);
// Hybrid TLS: combine with classical ECDH for defense-in-depth
// NIST recommends "hybrid" key exchange during transition period
🗺️ PQC Migration Roadmap
When to Use Which Algorithm
Choosing between PQC algorithms depends on your specific use case:
- Key Exchange / TLS: Use ML-KEM (Kyber-768). It's the most mature, fastest option for establishing shared secrets in TLS 1.3 handshakes.
- Code/Document Signing: Use ML-DSA (Dilithium3). Good balance of signing speed, verification speed, and key/signature sizes.
- Long-term Archival Signatures: Use SLH-DSA (SPHINCS+). The only hash-based option; relies on no lattice assumptions. Ideal when you need signatures to be verifiable for 30+ years.
- Resource-constrained IoT: Kyber-512 or Dilithium2 for smaller footprint, though still significantly larger than ECDSA.
Summary
Post-quantum cryptography is no longer theoretical — NIST has finalized three algorithms and both BouncyCastle and .NET 9 provide production-ready implementations. The transition from classical cryptography to PQC is a multi-year effort, but the time to start planning is now: "harvest now, decrypt later" attacks are already occurring, meaning adversaries are storing encrypted traffic today to decrypt once quantum computers arrive.