KyberNET 1.0.3

Prefix Reserved
dotnet add package KyberNET --version 1.0.3
                    
NuGet\Install-Package KyberNET -Version 1.0.3
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="KyberNET" Version="1.0.3" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="KyberNET" Version="1.0.3" />
                    
Directory.Packages.props
<PackageReference Include="KyberNET" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add KyberNET --version 1.0.3
                    
#r "nuget: KyberNET, 1.0.3"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package KyberNET@1.0.3
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=KyberNET&version=1.0.3
                    
Install as a Cake Addin
#tool nuget:?package=KyberNET&version=1.0.3
                    
Install as a Cake Tool

KyberNET

Pure C# implementation of Kyber ML-KEM (NIST FIPS 203)

NuGet License .NET C# Windows Linux macOS iOS Android

Introduction

KyberNET is a fully managed C# implementation of the Module-Lattice Key Encapsulation Mechanism (ML-KEM) standardized by NIST as FIPS 203 and derived from CRYSTALS-Kyber. Every primitive the algorithm needs (Keccak / SHA-3 / SHAKE absorption and squeezing, number-theoretic transform, sampling, Barrett and Montgomery arithmetic) are written in pure C# inside this repository. The shipping assembly has zero third-party runtime dependencies, contains no native code and no platform conditional compilation.

Why a portable, dependency-free implementation matters

.NET 10 was the first release to expose ML-KEM through the BCL as System.Security.Cryptography.MLKem. Part of the API is annotated as experimental and Microsoft's documentation defines its availability narrowly:

The PQC algorithms are available on systems where the system cryptographic libraries are OpenSSL 3.5 (or newer) or Windows CNG with PQC support.

In practice that excludes macOS, iOS, Android, WebAssembly, every Linux distribution that has not yet shipped OpenSSL 3.5, and any older Windows host without CNG PQC backports. Application authors in need of a single library artifact that behaves identically everywhere .NET runs cannot rely on the in-box implementation today.

KyberNET removes that constraint. Because the entire algorithm is written in managed code with no calls into the operating system's crypto subsystem (except the overridable built-in RNG API), the library produces bit-identical results on any runtime that loads a net10.0 or netstandard2.1 assembly. The same package powers ASP.NET Core services, .NET MAUI mobile apps, Blazor WebAssembly clients, Unity games, and command-line tools.

Capabilities (NIST FIPS 203)

All standardized parameter sets are implemented with full key generation, encapsulation and decapsulation.

Parameter NIST category Encapsulation key Decapsulation key Ciphertext Shared secret
ML-KEM-512 1 (AES-128) 800 bytes 1632 bytes 768 bytes 32 bytes
ML-KEM-768 3 (AES-192) 1184 bytes 2400 bytes 1088 bytes 32 bytes
ML-KEM-1024 5 (AES-256) 1568 bytes 3168 bytes 1568 bytes 32 bytes

Supported targets

The library multi-targets net10.0 and netstandard2.1. With no native dependency to constrain it, every platform and architecture where the .NET runtime is published is a valid host.

Target x64 Arm64 Arm32
Windows
Linux
macOS N/A*
Mac Catalyst N/A*
iOS / iPadOS (incl. Simulator) N/A*
Android
Browser (WebAssembly)
Unity (Mono / IL2CPP)

* N/A marks combinations where the .NET runtime itself is not produced by Microsoft, not a KyberNET limitation. The netstandard2.1 target additionally covers older hosts (examples: Mono 6.4+, Xamarin.iOS 12.16+, Xamarin.Android 10.0+, .NET Core 3.0+, Unity 2021.2+). An additional target of netstandard2.0 for full-framework use cases is not planned.

Validation

Lattice-based cryptography is famously easy to get wrong in hand-written constant-time code, so the implementation is checked against three independent oracles.

NIST Known Answer Tests

KyberNET.Testing.Kat ships an embedded JSON resource (TestData/kat-vectors.json) containing test vectors derived from NIST's ACVP example values for FIPS 203. For every parameter set, the vectors expose the deterministic inputs (d, z, m) together with the expected encapsulation key, decapsulation key, ciphertext + the shared key.

The Generate, Encapsulate, and Decapsulate nested test classes wire those seeds into KyberNET through a deterministic IRandomProvider, then compare the produced byte arrays against the published references with CollectionAssert.AreEqual. A mismatch on any single byte fails the suite, which is how the project catches regressions in NTT scheduling, Barrett constants, or compressed coefficient packing before they reach a release.

Bouncy Castle cross-validation

BouncyCastleCrossValidationTest.cs runs KyberNET and Bouncy Castle's MLKemKeyPairGenerator and MLKemPublicKeyParameters side by side under shared seeds. Two profiles are defined:

  • BouncyCastleQuick: 100 random round-trips per parameter set, included in the default test pass.
  • Comprehensive: 10,000 random round-trips per parameter set, kept out of routine runs because it adds roughly 45 seconds and is reserved for release validation.

Unit and integration coverage

KyberNET.Testing.Unit exercises every internal primitive (ModMath, PolyMath, Sampling, BitOps, the Keccak stack, all key container types) and KyberNET.Testing.Integration covers the higher-level scenarios that downstream consumers actually build, including the secure messaging and file encryption patterns shown below.

dotnet test                                                # full pass (unit + integration + KAT + BC quick)
dotnet test --filter "TestCategory!=Comprehensive"         # skip the 10k Bouncy Castle sweep
dotnet test --filter "TestCategory=KAT"                    # NIST vectors only
dotnet run --project test/KyberNET.Testing.Benchmark       # BenchmarkDotNet harness

Security posture

This is a software implementation. The .NET JIT does not guarantee constant-time execution, so side-channel resistance depends on the target platform and runtime version. The library has not undergone a formal third-party security audit. If your threat model includes timing or power-analysis attacks, evaluate accordingly before deploying to production.

Installation

dotnet add package KyberNET
<PackageReference Include="KyberNET" Version="1.0.3" />

Usage

ML-KEM is a key encapsulation mechanism, not a stream cipher. Its output is a 32-byte shared secret intended to drive symmetric encryption such as AES-GCM. The two examples below show the typical hybrid (ML-KEM + AES) pattern end to end.

Secure messaging

using System.Security.Cryptography;
using System.Text;
using KyberNET.Api;
using KyberNET.Keys;

// Bob generates a key pair and publishes the encapsulation key
var bobKeyPair = MlKem768.GenerateKeyPair();
var bobPublicKeyBytes = bobKeyPair.EncapsulationKey.FullBytes;
var bobPrivateKeyBytes = bobKeyPair.DecapsulationKey.FullBytes;

// Alice imports Bob's public key and encapsulates a fresh shared secret
var bobPublicKey = KyberEncapsulationKey.FromBytes(bobPublicKeyBytes);
var encapsulation = bobPublicKey.Encapsulate();

var aliceSharedSecret = encapsulation.SharedSecretKey;
var kemCipherText = encapsulation.CipherText.FullBytes;

// Alice encrypts the payload under the shared secret with AES-GCM
var nonce = RandomNumberGenerator.GetBytes(12);
var plaintext = Encoding.UTF8.GetBytes("Attack at dawn");
var ciphertext = new byte[plaintext.Length];
var tag = new byte[16];

using (var aes = new AesGcm(aliceSharedSecret, 16))
{
    aes.Encrypt(nonce, plaintext, ciphertext, tag);
}

// Bob decapsulates to recover the same shared secret and decrypts
var bobPrivateKey = KyberDecapsulationKey.FromBytes(bobPrivateKeyBytes);
var receivedKem = KyberCipherText.FromBytes(kemCipherText);
var bobSharedSecret = bobPrivateKey.Decapsulate(receivedKem);

var decrypted = new byte[ciphertext.Length];

using (var aes = new AesGcm(bobSharedSecret, 16))
{
    aes.Decrypt(nonce, ciphertext, tag, decrypted);
}

Console.WriteLine(Encoding.UTF8.GetString(decrypted)); // Attack at dawn

File encryption

Persisting a KEM transcript alongside an AES-GCM payload needs a small envelope so the reader can find each section. A single leading byte (K, taken from the chosen parameter set) makes the format self-describing across ML-KEM-512 / 768 / 1024.

using System.Security.Cryptography;
using KyberNET.Api;
using KyberNET.Constants;
using KyberNET.Keys;

static void EncryptFile(string inputPath, string outputPath, byte[] recipientPublicKey)
{
    var encapsulationKey = KyberEncapsulationKey.FromBytes(recipientPublicKey);
    var result = encapsulationKey.Encapsulate();

    var nonce = RandomNumberGenerator.GetBytes(12);
    var fileData = File.ReadAllBytes(inputPath);
    var ciphertext = new byte[fileData.Length];
    var tag = new byte[16];

    using (var aes = new AesGcm(result.SharedSecretKey, 16))
    {
        aes.Encrypt(nonce, fileData, ciphertext, tag);
    }

    using var output = File.Create(outputPath);
    output.WriteByte((byte)encapsulationKey.Parameter.K); // 2, 3, or 4 corresponds to the variant
    output.Write(result.CipherText.FullBytes);
    output.Write(nonce);
    output.Write(ciphertext);
    output.Write(tag);
}

static byte[] DecryptFile(string encryptedPath, byte[] recipientPrivateKey)
{
    var fileBytes = File.ReadAllBytes(encryptedPath);

    var parameter = fileBytes[0] switch
    {
        2 => KyberParameter.MlKem512,
        3 => KyberParameter.MlKem768,
        4 => KyberParameter.MlKem1024,
        _ => throw new InvalidDataException("Unknown ML-KEM variant")
    };

    var offset = 1;
    var kemBytes = fileBytes[offset..(offset + parameter.CiphertextLength)];
    offset += parameter.CiphertextLength;

    var nonce = fileBytes[offset..(offset + 12)];
    offset += 12;

    var ciphertext = fileBytes[offset..^16];
    var tag = fileBytes[^16..];

    var privateKey = KyberDecapsulationKey.FromBytes(recipientPrivateKey);
    var sharedSecret = privateKey.Decapsulate(KyberCipherText.FromBytes(kemBytes));

    var plaintext = new byte[ciphertext.Length];
    
    using (var aes = new AesGcm(sharedSecret, 16))
    {
        aes.Decrypt(nonce, ciphertext, tag, plaintext);
    }

    return plaintext;
}

See full examples for using the API under test/KyberNET.Testing.Integration/Api/.

Acknowledgements

The structure of the port, in particular the partitioning of K-PKE and ML-KEM operations and the way Keccak streams plug into sampling, was informed by KyberKotlin by Ron Hombre (documentation at kyber.hombre.asia). All algorithms and constants come directly from the FIPS 203 standard.

References

Product Compatible and additional computed target framework versions.
.NET net5.0 was computed.  net5.0-windows was computed.  net6.0 was computed.  net6.0-android was computed.  net6.0-ios was computed.  net6.0-maccatalyst was computed.  net6.0-macos was computed.  net6.0-tvos was computed.  net6.0-windows was computed.  net7.0 was computed.  net7.0-android was computed.  net7.0-ios was computed.  net7.0-maccatalyst was computed.  net7.0-macos was computed.  net7.0-tvos was computed.  net7.0-windows was computed.  net8.0 was computed.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
.NET Core netcoreapp3.0 was computed.  netcoreapp3.1 was computed. 
.NET Standard netstandard2.1 is compatible. 
MonoAndroid monoandroid was computed. 
MonoMac monomac was computed. 
MonoTouch monotouch was computed. 
Tizen tizen60 was computed. 
Xamarin.iOS xamarinios was computed. 
Xamarin.Mac xamarinmac was computed. 
Xamarin.TVOS xamarintvos was computed. 
Xamarin.WatchOS xamarinwatchos was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.
  • .NETStandard 2.1

    • No dependencies.
  • net10.0

    • No dependencies.

NuGet packages

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.3 73 6/2/2026
1.0.1 96 5/24/2026
1.0.0 95 5/21/2026