Quantum-resistant asymmetric encryption with Golang, ML-KEM and AES

With the release of Go 1.24 (currently in RC), the standard library introduces crypto/mlkem
, implementing the ML-KEM (Kyber) key encapsulation mechanism, a quantum-resistant cryptographic primitive standardized in NIST FIPS 203.
What is ML-KEM#
ML-KEM is a post-quantum key encapsulation method based on lattice-based cryptography. Unlike traditional public-key encryption schemes, which may be vulnerable to quantum attacks, ML-KEM provides a secure way to exchange symmetric encryption keys even in the presence of adversaries equipped with quantum computing capabilities.
AES-256 remains considered secure in a post-quantum world, as its key size is large enough to resist quantum attacks such as Grover’s algorithm, which would only halve its effective security level.
This addition marks a significant step in Go’s cryptographic ecosystem, enabling developers to future-proof applications by adopting post-quantum encryption techniques with minimal effort.
In the following example, we will demonstrate how to use the crypto/mlkem
package to securely encapsulate a key for AES-256 encryption.
Example of creation of a decapsulation key and an encapsulation key#
The GenerateKey1024
method from the crypto/mlkem
package generates a new decapsulation key (equivalent to a private key).
From the decapsulation key we can extract the encapsulation key (equivalent to a public key).
// Generate a new decapsulation key (equivalent to a private key)
decKey, err := mlkem.GenerateKey1024()
if err != nil {
panic("failed to generate decapsulation key")
}
// Extract the encapsulation key (equivalent to a public key)
encKey := decKey.EncapsulationKey()
Example of encryption using encapsulation key#
The Encapsulate
method takes no input and returns a shared key and its relative ciphertext.
The generated sharedKey
is a 256 bit symmetric key that can be used for AES encryption and the sharedKeyCiphertext
is the relative ciphertext encapsulating the shared key.
// Define a plaintext message
msg := []byte("This is a test message")
// Generate a new shared key and relative ciphertext
sharedKey, sharedKeyCiphertext := encKey.Encapsulate()
// sharedKey is a 256 bit symmetric key that can be used for AES encryption
// Encrypt with AES GCM 256
{
// Create a block cipher
block, err := aes.NewCipher(sharedKey)
if err != nil {
panic("failed to create cipher block")
}
// Create a GCM cipher
gcm, err := cipher.NewGCM(block)
if err != nil {
panic("failed to create GCM cipher")
}
// Generate a random nonce for each encryption
nonce := make([]byte, gcm.NonceSize())
if _, err := io.ReadFull(rand.Reader, nonce); err != nil {
panic("failed to generate nonce")
}
// Seal will append the ciphertext to the nonce
// The nonce is prepended to the ciphertext and will be used for decryption
msgCyphertext := gcm.Seal(nonce, nonce, msg, nil)
}
Example of decryption using decapsulation key#
The encrypted message in msgCyphertext
can be shared with the recipient, along with the encrypted key sharedKeyCiphertext
.
The recipient, in order to decrypt the message, must have the decapsulation key decKey
from which the encapsulation key used to generate sharedKeyCiphertext
was derived.
sharedKey, e := decKey.Decapsulate(sharedKeyCiphertext)
if e != nil {
panic("failed to decapsulate key")
return
}
// Create a block cipher
block, err := aes.NewCipher(sharedKey)
if err != nil {
panic("failed to create block cipher")
}
// Create a GCM cipher
gcm, err := cipher.NewGCM(block)
if err != nil {
panic("failed to create GCM cipher")
}
// Extract the nonce from the ciphertext
nonceSize := gcm.NonceSize()
if len(msgCyphertext) < nonceSize {
panic("ciphertext is too short")
}
// Extract the nonce from the ciphertext
nonce := msgCyphertext[:nonceSize]
if len(nonce) != nonceSize {
panic("invalid nonce size")
}
// Extract the actual ciphertext after the nonce
encryptedData := msgCyphertext[nonceSize:]
if len(encryptedData) < 1 {
panic("ciphertext is too short")
}
msg, err = gcm.Open(nil, nonce, encryptedData, nil)
if err != nil {
panic("failed to decrypt message")
}
Conclusion#
Post-quantum security has become increasingly crucial due to the potential threat posed by quantum computers. It is advisable to adopt encryption algorithms that are resilient against quantum attacks to safeguard sensitive information. Implementing post-quantum encryption now is a proactive step to ensure that data remains secure, even as advancements in quantum computing continue. By doing so, we can mitigate the risk of future data breaches and maintain the confidentiality of critical information.