ML-KEM (Module-Lattice-Based Key-Encapsulation Mechanism) support
Introduction
The IPWorks Encrypt development library supports Module-Lattice-Based Key-Encapsulation Mechanism (ML-KEM) cryptography via the MLKEM component. ML-KEM is the NIST-standardized post-quantum key-encapsulation mechanism defined in FIPS 203 (based on the CRYSTALS-Kyber algorithm), designed to remain secure even against an attacker with a quantum computer. Like a Diffie-Hellman exchange, it lets two parties agree on a shared secret over a channel that may be observed by a third party, but it does so with a different shape of exchange, described below.
This guide covers creating ML-KEM keys and establishing a shared secret between two parties.
ML-KEM Keys
The MLKEM component supports three parameter sets, selected via the ParamSet config:
- 512 (default)
- 768
- 1024
Higher parameter sets provide a larger security margin, at the cost of larger keys/ciphertexts and more computation. All parties in an exchange must agree on the same ParamSet ahead of time, since it is not negotiated by the component.
To generate a new key pair, set ParamSet as needed and call CreateKey. The generated public and private key values are then available, as raw bytes, from the Key property:
MLKEM mlkem = new MLKEM();
mlkem.Config("ParamSet=768");
mlkem.CreateKey();
byte[] publicKey = mlkem.Key.PublicKeyB;
byte[] privateKey = mlkem.Key.PrivateKeyB;
Unlike a Diffie-Hellman exchange, where both sides generate a key pair, only one side of an ML-KEM exchange needs to do so. The other side only ever needs to see that public key.
Establishing a Shared Secret
ML-KEM arrives at a shared secret asymmetrically. One party (the recipient) generates a key pair and shares only its public key, while the other party (the sender) uses that public key to derive the secret and produce a ciphertext that only the matching private key can unwrap. For example, when using instances of our component as both the sender and recipient:
- The recipient calls CreateKey and sends the public key from Key to the sender.
- The sender sets the recipient's public key into its own Key property and calls Encapsulate. This populates SharedSecret with the derived secret, along with CipherText to send back to the recipient.
- The recipient sets CipherText to the received value and calls Decapsulate, which uses the private key in its own Key to populate its own SharedSecret with the same value the sender derived.
SharedSecret and CipherText are provided as raw bytes by default. Set UseHex to true before calling Encapsulate or Decapsulate to work with them as hex-encoded strings instead.
Code Sample
Below is an example of establishing a shared secret between two parties in C#.
// Recipient generates a key pair
MLKEM recipient = new MLKEM();
recipient.Config("ParamSet=768");
recipient.CreateKey();
byte[] recipientPublicKey = recipient.Key.PublicKeyB;
// Transmit the public key to the sender
// Sender encapsulates a shared secret using the recipient's public key
MLKEM sender = new MLKEM();
sender.Config("ParamSet=768");
sender.Key.PublicKeyB = recipientPublicKey;
sender.Encapsulate();
byte[] senderSecret = sender.SharedSecretB;
byte[] cipherText = sender.CipherTextB;
// Transmit the ciphertext to the recipient
// Recipient decapsulates the ciphertext using its private key
recipient.CipherTextB = cipherText;
recipient.Decapsulate();
byte[] recipientSecret = recipient.SharedSecretB;
// recipientSecret now equals senderSecret
if (AreEqual(senderSecret, recipientSecret)) {
Console.WriteLine("Equal: shared secrets match");
} else {
Console.WriteLine("Problem: shared secrets do not match, ciphertext was likely tampered with");
}
NOTE: This component is designed such that Decapsulate does not fail just because CipherText has been altered in transit, so long as it is still the correct length for the ParamSet in use.
Rather than raising an error, decapsulating a tampered ciphertext of valid length simply produces a SharedSecret that will not match the sender's. This is a deliberate property of the algorithm (implicit rejection), so applications that need to confirm both sides derived the same secret should verify this themselves. However, a ciphertext of the wrong length is rejected and causes Decapsulate to raise an error.
We appreciate your feedback. If you have any questions, comments, or suggestions about this article please contact our support team at support@nsoftware.com.