KyberNET 1.0.0
Prefix ReservedSee the version list below for details.
dotnet add package KyberNET --version 1.0.0
NuGet\Install-Package KyberNET -Version 1.0.0
<PackageReference Include="KyberNET" Version="1.0.0" />
<PackageVersion Include="KyberNET" Version="1.0.0" />
<PackageReference Include="KyberNET" />
paket add KyberNET --version 1.0.0
#r "nuget: KyberNET, 1.0.0"
#:package KyberNET@1.0.0
#addin nuget:?package=KyberNET&version=1.0.0
#tool nuget:?package=KyberNET&version=1.0.0
KyberNET
Pure C# implementation of Kyber ML-KEM (NIST FIPS 203)
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
NuGet publication is pending.
dotnet add package KyberNET
<PackageReference Include="KyberNET" Version="1.0.0" />
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
- NIST FIPS 203: Module-Lattice-Based Key-Encapsulation Mechanism Standard
- CRYSTALS-Kyber, Round 3 specification
- NIST Post-Quantum Cryptography project
- NIST ACVP example values
- KyberKotlin
| Product | Versions 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. |
-
.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.