Skip to content

TimothyMeadows/ML-KEM.NetCore

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

ML-KEM.NetCore

License: MIT nuget

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.


Table of contents


Requirements

  • .NET 8 SDK for building and running tests.
  • Project target framework: net8.0.

Installation

From source

Clone the repository and reference the MLKEM.NetCore project from your solution.

NuGet (when published)

dotnet add package ML-KEM.NetCore

Quick start

Pinned-memory encapsulation flow

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

Alice/Bob key exchange flow

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}");
    }
}

API reference

MLKemParameterSet

enum MLKemParameterSet
{
    MLKem512,
    MLKem768,
    MLKem1024
}

Selects the NIST parameter set used by an MLKem instance.


MLKem

Constructor

MLKem(MLKemParameterSet parameterSet)

Size properties

int PublicKeyBytes { get; }
int SecretKeyBytes { get; }
int CipherTextBytes { get; }
int SharedSecretBytes { get; } // always 32

Key generation

SecureKeyPair GenerateKeyPair()
SecureKeyPair GenerateKeyPair(ReadOnlySpan<byte> d, ReadOnlySpan<byte> z)
  • Deterministic generation requires d and z to be exactly 32 bytes each.

Encapsulation

EncapsulationResult Encapsulate(ReadOnlySpan<byte> publicKey)
EncapsulationResult EncapsulateDeterministic(ReadOnlySpan<byte> publicKey, ReadOnlySpan<byte> m)
  • EncapsulateDeterministic(...) requires m to be exactly 32 bytes.

Decapsulation

PinnedMemory<byte> Decapsulate(ReadOnlySpan<byte> secretKey, ReadOnlySpan<byte> cipherText)
  • Input lengths are validated and must match the selected parameter set sizes.

Result types

sealed class SecureKeyPair
{
    byte[] PublicKey { get; }
    PinnedMemory<byte> SecretKey { get; }
}

sealed class EncapsulationResult
{
    byte[] CipherText { get; }
    PinnedMemory<byte> SharedSecret { get; }
}

Parameter sizes

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

Best practices

  1. Use secure APIs for secret-bearing values

    • Use GenerateKeyPair, Encapsulate, and Decapsulate to keep key material in PinnedMemory<byte>.
    • Dispose PinnedMemory<byte> instances promptly.
  2. Treat parameter sets as protocol constants

    • Do not mix parameter sets between peers.
    • Validate all serialized key/ciphertext lengths before use.
  3. Prefer deterministic APIs only for tests/vectors

    • In production, use randomized GenerateKeyPair() and Encapsulate().
  4. Keep secret data lifetime short

    • Zero and dispose sensitive buffers as soon as possible.

Validation and testing

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

Development

Build

dotnet build MLKEM.sln

Test

dotnet test MLKEM.sln

Security notes

This 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 z on 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.

License

MIT License. See LICENSE.

About

ML-KEM.NetCore is a pure managed .NET implementation of the NIST-standardized ML-KEM (Kyber) post-quantum key encapsulation mechanism.

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Languages