ML-KEM.NetCore is a pure managed .NET implementation of the NIST-standardized ML-KEM (Kyber) post-quantum key encapsulation mechanism.
The library targets .NET 8 and uses PinnedMemory for secret key and shared secret material to improve lifecycle control.
- Requirements
- Installation
- Quick start
- API reference
- Parameter sizes
- Best practices
- Validation and testing
- Development
- Security notes
- License
- .NET 8 SDK for building and running tests.
- Project target framework:
net8.0.
Clone the repository and reference the MLKEM.NetCore project from your solution.
dotnet add package ML-KEM.NetCoreusing MLKEM.NetCore;
var kem = new MLKem(MLKemParameterSet.MLKem768);
var keyPair = kem.GenerateKeyPair();
using (keyPair.SecretKey)
{
var encapsulation = kem.Encapsulate(keyPair.PublicKey);
using (encapsulation.SharedSecret)
{
var secretKey = keyPair.SecretKey.Read().AsSpan(0, kem.SecretKeyBytes);
using var sharedSecretBob = kem.Decapsulate(secretKey, encapsulation.CipherText);
// Compare or use shared secrets, then dispose to scrub.
}
}using System.Security.Cryptography;
using MLKEM.NetCore;
var kem = new MLKem(MLKemParameterSet.MLKem768);
// Alice creates a long-term key pair and shares her public key.
var aliceKeyPair = kem.GenerateKeyPair();
using (aliceKeyPair.SecretKey)
{
// Bob encapsulates to Alice's public key.
var bobEncapsulation = kem.Encapsulate(aliceKeyPair.PublicKey);
using (bobEncapsulation.SharedSecret)
{
var bobSharedSecret = bobEncapsulation.SharedSecret.Read().AsSpan(0, kem.SharedSecretBytes);
// Alice decapsulates Bob's ciphertext using her secret key.
var aliceSecretKey = aliceKeyPair.SecretKey.Read().AsSpan(0, kem.SecretKeyBytes);
using var aliceSharedSecretPinned = kem.Decapsulate(aliceSecretKey, bobEncapsulation.CipherText);
var aliceSharedSecret = aliceSharedSecretPinned.Read().AsSpan(0, kem.SharedSecretBytes);
var sameSecret = CryptographicOperations.FixedTimeEquals(bobSharedSecret, aliceSharedSecret);
Console.WriteLine($"Alice and Bob share the same secret: {sameSecret}");
}
}enum MLKemParameterSet
{
MLKem512,
MLKem768,
MLKem1024
}Selects the NIST parameter set used by an MLKem instance.
MLKem(MLKemParameterSet parameterSet)int PublicKeyBytes { get; }
int SecretKeyBytes { get; }
int CipherTextBytes { get; }
int SharedSecretBytes { get; } // always 32SecureKeyPair GenerateKeyPair()
SecureKeyPair GenerateKeyPair(ReadOnlySpan<byte> d, ReadOnlySpan<byte> z)- Deterministic generation requires
dandzto be exactly 32 bytes each.
EncapsulationResult Encapsulate(ReadOnlySpan<byte> publicKey)
EncapsulationResult EncapsulateDeterministic(ReadOnlySpan<byte> publicKey, ReadOnlySpan<byte> m)EncapsulateDeterministic(...)requiresmto be exactly 32 bytes.
PinnedMemory<byte> Decapsulate(ReadOnlySpan<byte> secretKey, ReadOnlySpan<byte> cipherText)- Input lengths are validated and must match the selected parameter set sizes.
sealed class SecureKeyPair
{
byte[] PublicKey { get; }
PinnedMemory<byte> SecretKey { get; }
}
sealed class EncapsulationResult
{
byte[] CipherText { get; }
PinnedMemory<byte> SharedSecret { get; }
}Per instantiated MLKem object:
MLKem512- Public key: 800 bytes
- Secret key: 1632 bytes
- Ciphertext: 768 bytes
- Shared secret: 32 bytes
MLKem768- Public key: 1184 bytes
- Secret key: 2400 bytes
- Ciphertext: 1088 bytes
- Shared secret: 32 bytes
MLKem1024- Public key: 1568 bytes
- Secret key: 3168 bytes
- Ciphertext: 1568 bytes
- Shared secret: 32 bytes
-
Use secure APIs for secret-bearing values
- Use
GenerateKeyPair,Encapsulate, andDecapsulateto keep key material inPinnedMemory<byte>. - Dispose
PinnedMemory<byte>instances promptly.
- Use
-
Treat parameter sets as protocol constants
- Do not mix parameter sets between peers.
- Validate all serialized key/ciphertext lengths before use.
-
Prefer deterministic APIs only for tests/vectors
- In production, use randomized
GenerateKeyPair()andEncapsulate().
- In production, use randomized
-
Keep secret data lifetime short
- Zero and dispose sensitive buffers as soon as possible.
The test suite includes:
- Deterministic known-answer-style vector checks
- Encapsulation/decapsulation round-trip checks across parameter sets
- Ciphertext tamper rejection behavior
- Secure API behavior and memory handling coverage
Run all tests:
dotnet test MLKEM.slndotnet build MLKEM.slndotnet test MLKEM.slnThis implementation follows ML-KEM constructions in RFC 9936, including a rejection-sampling path that continues SHAKE output generation until enough coefficients are produced.
Current hardening in this repository:
- Decapsulation uses constant-time ciphertext validation with fallback to
zon invalid ciphertext. - Sensitive intermediates in decapsulation are explicitly zeroed before returning.
- All secret key and shared secret APIs expose
PinnedMemory<byte>for deterministic cleanup of secret-bearing values. - Public keys and ciphertext remain
byte[]for interoperability, while key material is pinned.
MIT License. See LICENSE.