.NET Post-Quantum Cryptography Guide
Starting with .NET 10 (November 2025), the System.Security.Cryptography namespace includes native support for all three NIST post-quantum standards. This guide covers practical usage patterns.
Prerequisites
- .NET 10 or later
- Windows with a CNG provider that supports the algorithms, or Linux with OpenSSL 3.5+
- No additional NuGet packages required -- PQC support is in the base class library
ML-KEM -- Key Encapsulation
ML-KEM replaces Diffie-Hellman and ECDH for establishing shared secrets. Instead of both parties contributing to a shared calculation, one party encapsulates a secret and the other decapsulates it.
using System.Security.Cryptography;
// Recipient generates a key pair
using MLKem recipient = MLKem.Create(MLKemParameterSet.MLKem768);
byte[] publicKey = recipient.ExportPublicKey();
// Sender encapsulates a shared secret using recipient's public key
using MLKem sender = MLKem.Create(MLKemParameterSet.MLKem768);
sender.ImportPublicKey(publicKey);
MLKemEncapsulationResult result = sender.Encapsulate();
byte[] ciphertext = result.Ciphertext; // Send this to recipient
byte[] sharedSecret = result.SharedSecret; // Use this for symmetric encryption
// Recipient decapsulates to recover the same shared secret
byte[] recoveredSecret = recipient.Decapsulate(ciphertext);
// sharedSecret and recoveredSecret are identical (32 bytes)
Choosing a Parameter Set
| Parameter Set | Security Level | Use When |
|---|---|---|
MLKemParameterSet.MLKem512 |
NIST Level 1 | Performance-critical, shorter-lived secrets |
MLKemParameterSet.MLKem768 |
NIST Level 3 | Recommended default for most applications |
MLKemParameterSet.MLKem1024 |
NIST Level 5 | Highest-assurance, long-term secrets |
ML-DSA -- Digital Signatures
ML-DSA replaces RSA and ECDSA for digital signatures. It provides fast signing and verification with strong post-quantum security.
using System.Security.Cryptography;
// Generate signing key pair
using MLDsa signer = MLDsa.Create(MLDsaParameterSet.MLDsa65);
byte[] publicKey = signer.ExportPublicKey();
// Sign a message
byte[] message = System.Text.Encoding.UTF8.GetBytes("Critical firmware v2.1.0");
byte[] signature = signer.SignData(message);
// Verify (typically on a different machine, using only the public key)
using MLDsa verifier = MLDsa.Create(MLDsaParameterSet.MLDsa65);
verifier.ImportPublicKey(publicKey);
bool isValid = verifier.VerifyData(message, signature);
Choosing a Parameter Set
| Parameter Set | Security Level | Signature Size | Use When |
|---|---|---|---|
MLDsaParameterSet.MLDsa44 |
NIST Level 2 | 2,420 bytes | Bandwidth-constrained, standard assurance |
MLDsaParameterSet.MLDsa65 |
NIST Level 3 | 3,293 bytes | Recommended default |
MLDsaParameterSet.MLDsa87 |
NIST Level 5 | 4,595 bytes | Highest assurance, long-lived signatures |
SLH-DSA -- Hash-Based Signatures
SLH-DSA is the conservative choice -- its security relies only on the well-understood properties of hash functions, not on the hardness of lattice problems.
using System.Security.Cryptography;
// Generate key pair (slower than ML-DSA)
using SlhDsa signer = SlhDsa.Create(SlhDsaParameterSet.SlhDsaSha2_128s);
byte[] publicKey = signer.ExportPublicKey(); // Only 32 bytes!
// Sign (slower than ML-DSA; signatures are large)
byte[] message = System.Text.Encoding.UTF8.GetBytes("Root CA certificate payload");
byte[] signature = signer.SignData(message); // 7,856 bytes for 128s
// Verify
using SlhDsa verifier = SlhDsa.Create(SlhDsaParameterSet.SlhDsaSha2_128s);
verifier.ImportPublicKey(publicKey);
bool isValid = verifier.VerifyData(message, signature);
Warning: SLH-DSA signing is significantly slower than ML-DSA (milliseconds vs microseconds). Use it for root-of-trust operations where conservative security assumptions matter more than throughput.
Hybrid Encryption Pattern
For the transition period, combine classical and post-quantum algorithms. If either is broken, the other still protects:
using System.Security.Cryptography;
// Hybrid key exchange: X25519 + ML-KEM-768
// Each party performs both exchanges; the shared secret is the concatenation
// Party A
using var ecdh = ECDiffieHellman.Create(ECCurve.NamedCurves.Curve25519);
using var mlkem = MLKem.Create(MLKemParameterSet.MLKem768);
byte[] ecdhPublic = ecdh.PublicKey.ExportSubjectPublicKeyInfo();
byte[] mlkemPublic = mlkem.ExportPublicKey();
// Send both public keys to Party B
// Party B (receives A's public keys, generates shared secrets)
using var ecdhB = ECDiffieHellman.Create(ECCurve.NamedCurves.Curve25519);
byte[] ecdhShared = ecdhB.DeriveKeyMaterial(/* A's ECDH public key */);
using var mlkemB = MLKem.Create(MLKemParameterSet.MLKem768);
mlkemB.ImportPublicKey(mlkemPublic);
var encapResult = mlkemB.Encapsulate();
byte[] mlkemShared = encapResult.SharedSecret;
byte[] mlkemCiphertext = encapResult.Ciphertext;
// Send ecdhB.PublicKey + mlkemCiphertext back to A
// Combine both shared secrets using a KDF
byte[] combinedSecret = SHA256.HashData(
[.. ecdhShared, .. mlkemShared]
);
// Use combinedSecret as the symmetric key
Key Serialisation and Storage
// Export keys in standard formats
byte[] privateKeyPkcs8 = signer.ExportPkcs8PrivateKey();
byte[] publicKeySpki = signer.ExportSubjectPublicKeyInfo();
// Import from byte arrays
using var imported = MLDsa.Create(MLDsaParameterSet.MLDsa65);
imported.ImportPkcs8PrivateKey(privateKeyPkcs8, out _);
// PEM format (for certificates, config files)
string pem = signer.ExportSubjectPublicKeyInfoPem();
Integration with ASP.NET Core
// In an ASP.NET Core controller -- sign an API response token
[HttpGet("/api/signed-payload")]
public IActionResult GetSignedPayload()
{
var payload = new { data = "sensitive", timestamp = DateTime.UtcNow };
byte[] payloadBytes = JsonSerializer.SerializeToUtf8Bytes(payload);
using var signer = MLDsa.Create(MLDsaParameterSet.MLDsa65);
signer.ImportPkcs8PrivateKey(_signingKey, out _);
byte[] signature = signer.SignData(payloadBytes);
return Ok(new
{
payload = Convert.ToBase64String(payloadBytes),
signature = Convert.ToBase64String(signature),
algorithm = "ML-DSA-65"
});
}
Migration Strategy
- Audit -- Use CipherShift365 Compass to identify all cryptographic usage in your solution.
- Prioritise -- Start with key exchange (ML-KEM) since "harvest now, decrypt later" is the primary threat.
- Hybrid first -- Deploy hybrid (classical + PQC) to maintain backward compatibility.
- Monitor -- Use CipherShift365 Guardian to ensure no regression to quantum-vulnerable algorithms.
- Pure PQC -- Once hybrid is stable and classical support is no longer required, remove the classical component.
Platform Support Matrix
| Platform | ML-KEM | ML-DSA | SLH-DSA | Backend |
|---|---|---|---|---|
| Windows 11 24H2+ | Yes | Yes | Yes | CNG |
| Windows Server 2025+ | Yes | Yes | Yes | CNG |
| Linux (OpenSSL 3.5+) | Yes | Yes | Yes | OpenSSL |
| macOS (future) | Pending | Pending | Pending | Pending Apple CryptoKit |
| Azure Key Vault | Planned | Planned | Planned | HSM-backed |
Note: On platforms where the underlying OS provider does not yet support PQC, the .NET runtime throws
PlatformNotSupportedException. Always checkMLKem.IsSupported,MLDsa.IsSupported, orSlhDsa.IsSupportedbefore use.