365Architect

.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.

CSHARP
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.

CSHARP
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.

CSHARP
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:

CSHARP
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

CSHARP
// 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

CSHARP
// 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

  1. Audit -- Use CipherShift365 Compass to identify all cryptographic usage in your solution.
  2. Prioritise -- Start with key exchange (ML-KEM) since "harvest now, decrypt later" is the primary threat.
  3. Hybrid first -- Deploy hybrid (classical + PQC) to maintain backward compatibility.
  4. Monitor -- Use CipherShift365 Guardian to ensure no regression to quantum-vulnerable algorithms.
  5. 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 check MLKem.IsSupported, MLDsa.IsSupported, or SlhDsa.IsSupported before use.


Share

Keyboard Shortcuts

⌘ K
Open search
/
Focus search
?
Show shortcuts
b
Toggle bookmark
Alt+←
Previous page
Alt+→
Next page
Esc
Close overlay