Encapsulate and decapsulate using KEMs

This document describes using key encapsulation mechanisms (KEMs) with Cloud KMS keys to establish shared secrets.

Encapsulation uses the public key of the KEM key pair, and decapsulation uses the private key of the key pair. Cloud KMS lets you retrieve the public key, which you can then use with standard libraries to encapsulate your shared secret. To decapsulate the shared secret, use Cloud KMS decapsulation methods. You can't use the private key material outside of Cloud KMS.

Before you begin

  • This document provides examples that run at the command line. To simplify using the examples, use Cloud Shell. The encryption example uses OpenSSL, which is pre-installed on Cloud Shell. Otherwise, install OpenSSL on your machine.
  • Create a KEM key with key purpose of KEY_ENCAPSULATION. To see which algorithms are supported for key purpose KEY_ENCAPSULATION, see key encapsulation algorithms.

Grant permissions on the key

For more information about permissions and roles in Cloud KMS, see Permissions and Roles.

Encapsulation

To encapsulate using a KEM key, retrieve the public key and use the public key to encapsulate.

gcloud

This sample requires OpenSSL to be installed on your local system.

Download the public key

gcloud kms keys versions get-public-key KEY_VERSION \
    --key KEY_NAME \
    --keyring KEY_RING \
    --location LOCATION  \
    --output-file PUBLIC_KEY_FILE \
    --public-key-format PUBLIC_KEY_FORMAT

Replace the following:

  • KEY_VERSION: The version number of the key that you want to use for encapsulation—for example, 2.
  • KEY_NAME: The name of the key that you want to use for encapsulation.
  • KEY_RING: the name of the key ring that contains the key.
  • LOCATION: the Cloud KMS location of the key ring.
  • PUBLIC_KEY_FILE: The local file path where the public key will be saved.
  • PUBLIC_KEY_FORMAT: The target format for the public key—for example, nist-pqc. The default format is pem.

Reformat the public key

The encapsulate command requires the public key to be in PEM format. If you have downloaded the public key in another format, like nist-pqc, then you must convert the key to PEM format. If your public key is already in the PEM format, continue from Encapsulate.

Use the following command to convert the public key for a ML-KEM-768 key:

{ echo -n "MIIEsjALBglghkgBZQMEBAIDggShAA==" | base64 -d ; cat PUBLIC_KEY_FILE; } | \
openssl pkey -inform DER -pubin -pubout -out PEM_PUBLIC_KEY_FILE

Use the following command to convert the public key for a ML-KEM-1024 key:

{ echo -n "MIIGMjALBglghkgBZQMEBAMDggYhAA==" | base64 -d ; cat PUBLIC_KEY_FILE; } | \
openssl pkey -inform DER -pubin -pubout -out PEM_PUBLIC_KEY_FILE

Replace the following:

  • PUBLIC_KEY_FILE: The path to the downloaded public key file in raw format.
  • PEM_PUBLIC_KEY_FILE: The path and file name to save the public key in PEM format.

Encapsulate

To create a shared secret and ciphertext, you can use the following command:

openssl pkeyutl \
    -encap \
    -pubin \
    -inkey PEM_PUBLIC_KEY_FILE \
    -out CIPHERTEXT_FILE \
    -secret SHARED_SECRET_FILE

Replace the following:

  • PEM_PUBLIC_KEY_FILE: The path to the downloaded public key file in PEM format.
  • CIPHERTEXT_FILE: The path where you want to save the resulting ciphertext.
  • SHARED_SECRET_FILE: The path where you want to save the resulting shared secret.

Go

To run this code, first set up a Go development environment and install the Cloud KMS Go SDK.

import (
	"context"
	"crypto/mlkem"
	"fmt"
	"hash/crc32"
	"io"

	kms "cloud.google.com/go/kms/apiv1"
	"cloud.google.com/go/kms/apiv1/kmspb"
)

// encapsulateMLKEM demonstrates how to encapsulate a shared secret using an ML-KEM-768 public key
// from Cloud KMS.
func encapsulateMLKEM(w io.Writer, keyVersionName string) error {
	// keyVersionName := "projects/my-project/locations/us-east1/keyRings/my-key-ring/cryptoKeys/my-key/cryptoKeyVersions/1"

	// Create the client.
	ctx := context.Background()
	client, err := kms.NewKeyManagementClient(ctx)
	if err != nil {
		return fmt.Errorf("failed to create kms client: %w", err)
	}
	defer client.Close()

	// crc32c calculates the CRC32C checksum of the given data.
	crc32c := func(data []byte) uint32 {
		t := crc32.MakeTable(crc32.Castagnoli)
		return crc32.Checksum(data, t)
	}

	// Build the request to get the public key in NIST PQC format.
	req := &kmspb.GetPublicKeyRequest{
		Name:            keyVersionName,
		PublicKeyFormat: kmspb.PublicKey_NIST_PQC,
	}

	// Call the API to get the public key.
	response, err := client.GetPublicKey(ctx, req)
	if err != nil {
		return fmt.Errorf("failed to get public key: %w", err)
	}

	// Optional, but recommended: perform integrity verification on the response.
	// For more details on ensuring E2E in-transit integrity to and from Cloud KMS visit:
	// https://cloud.google.com/kms/docs/data-integrity-guidelines
	if response.GetName() != req.GetName() {
		return fmt.Errorf("GetPublicKey: request corrupted in-transit")
	}
	if response.GetPublicKeyFormat() != req.GetPublicKeyFormat() {
		return fmt.Errorf("GetPublicKey: request corrupted in-transit"