使用 KEM 封裝和解封裝

本文說明如何搭配使用金鑰封裝機制 (KEM) 和 Cloud KMS 金鑰,建立共用密鑰。

封裝作業會使用 KEM 金鑰組的公開金鑰,解封裝作業則會使用金鑰組的私密金鑰。Cloud KMS 可讓您擷取公開金鑰,然後搭配標準程式庫封裝共用密鑰。如要解除共用密鑰的封裝,請使用 Cloud KMS 解除封裝方法。您無法在 Cloud KMS 外部使用私密金鑰內容。

事前準備

  • 本文提供的範例使用指令列執行。如要簡化範例的使用程序,請使用 Cloud Shell。加密範例使用的是 OpenSSL,已經預先安裝在 Cloud Shell 中。否則,請在電腦上安裝 OpenSSL。
  • 建立 key purposeKEY_ENCAPSULATIONKEM 金鑰。 如要查看金鑰用途 KEY_ENCAPSULATION 支援的演算法,請參閱金鑰封裝演算法

授予金鑰權限

如要進一步瞭解 Cloud KMS 的權限和角色,請參閱「權限和角色」。

封裝

如要使用 KEM 金鑰封裝,請擷取公開金鑰,並使用公開金鑰封裝。

gcloud

這個範例需要在本機系統上安裝 OpenSSL

下載公開金鑰

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

更改下列內容:

  • KEY_VERSION:您要用於封裝的金鑰版本號碼,例如 2
  • KEY_NAME:要用於封裝的鍵名稱。
  • KEY_RING:金鑰所屬金鑰環的名稱。
  • LOCATION:金鑰環的 Cloud KMS 位置。
  • PUBLIC_KEY_FILE:公開金鑰的儲存路徑。
  • PUBLIC_KEY_FORMAT:公開金鑰的目標格式,例如 nist-pqc。預設格式為 pem

重新設定公開金鑰格式

封裝指令需要 PEM 格式的公開金鑰。如果您下載的公開金鑰是其他格式 (例如 nist-pqc),則必須將金鑰轉換為 PEM 格式。如果公開金鑰已採用 PEM 格式,請繼續執行「封裝」步驟。

使用下列指令轉換 ML-KEM-768 金鑰的公開金鑰:

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

使用下列指令轉換 ML-KEM-1024 金鑰的公開金鑰:

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

更改下列內容:

  • PUBLIC_KEY_FILE:下載的原始格式公開金鑰檔案路徑。
  • PEM_PUBLIC_KEY_FILE:以 PEM 格式儲存公開金鑰的路徑和檔案名稱。

封裝

如要建立共用密鑰和密文,可以使用下列指令:

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

更改下列內容:

  • PEM_PUBLIC_KEY_FILE:PEM 格式的已下載公開金鑰檔案路徑。
  • CIPHERTEXT_FILE:要儲存產生的密文的路徑。
  • SHARED_SECRET_FILE:您要儲存產生的共用密鑰路徑。

Go

如要執行這段程式碼,請先設定 Go 開發環境,並安裝 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")
	}
	if int64(crc32c(response.GetPublicKey().GetData())) != response.GetPublicKey().GetCrc32CChecksum().GetValue() {
		return fmt.Errorf("GetPublicKey: response corrupted in-transit")
	}

	// Use the public key with crypto/mlkem to encapsulate a shared secret.
	ek, err := mlkem.NewEncapsulationKey768(response.GetPublicKey().GetData())
	if err != nil {
		return fmt.Errorf("NewEncapsulationKey768: %w", err)
	}
	sharedSecret, ciphertext := ek.Encapsulate()

	fmt.Fprintf(w, "Encapsulated ciphertext: %x\n", ciphertext)
	fmt.Fprintf(w, "Shared secret: %x\n", sharedSecret)
	return nil
}

解封裝

使用 Cloud KMS 解除密文封裝。

gcloud

如要在指令列上使用 Cloud KMS,請先安裝或升級至最新版本的 Google Cloud CLI

gcloud kms decapsulate \
    --version KEY_VERSION \
    --key KEY_NAME \
    --keyring KEY_RING \
    --location LOCATION  \
    --ciphertext-file CIPHERTEXT_FILE \
    --shared-secret-file SHARED_SECRET_FILE

更改下列內容:

  • KEY_VERSION:用於解封裝的密鑰版本,例如 3
  • KEY_NAME:用於解封裝的金鑰名稱。
  • KEY_RING:金鑰所屬金鑰環的名稱。
  • LOCATION:金鑰環的 Cloud KMS 位置。
  • CIPHERTEXT_FILE:輸入密文的本機檔案路徑。
  • SHARED_SECRET_FILE:用於儲存輸出共用密鑰的本機檔案路徑。

Go

如要執行這段程式碼,請先設定 Go 開發環境,並安裝 Cloud KMS Go SDK

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

	kms "cloud.google.com/go/kms/apiv1"
	"cloud.google.com/go/kms/apiv1/kmspb"
	"google.golang.org/protobuf/types/known/wrapperspb"
)

// decapsulate decapsulates the given ciphertext using a saved private key of purpose
// KEY_ENCAPSULATION stored in KMS.
func decapsulate(w io.Writer, keyVersionName string, ciphertext []byte) error {
	// keyVersionName := "projects/my-project/locations/us-east1/keyRings/my-key-ring/cryptoKeys/my-key/cryptoKeyVersions/1"
	// ciphertext := []byte("...")

	// 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)
	}

	// Optional but recommended: Compute ciphertext's CRC32C.
	ciphertextCRC32C := crc32c(ciphertext)

	// Build the request.
	req := &kmspb.DecapsulateRequest{
		Name:             keyVersionName,
		Ciphertext:       ciphertext,
		CiphertextCrc32C: wrapperspb.Int64(int64(ciphertextCRC32C)),
	}

	// Call the API.
	result, err := client.Decapsulate(ctx, req)
	if err != nil {
		return fmt.Errorf("failed to decapsulate: %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 !result.GetVerifiedCiphertextCrc32C() {
		return fmt.Errorf("Decapsulate: request corrupted in-transit")
	}
	if result.GetName() != req.GetName() {
		return fmt.Errorf("Decapsulate: request corrupted in-transit")
	}
	if int64(crc32c(result.GetSharedSecret())) != result.GetSharedSecretCrc32C() {
		return fmt.Errorf("Decapsulate: response corrupted in-transit")
	}

	fmt.Fprintf(w, "Decapsulated plaintext: %x", result.GetSharedSecret())
	return nil
}

API

這些範例使用 curl 做為 HTTP 用戶端,示範如何使用 API。如要進一步瞭解存取權控管,請參閱「存取 Cloud KMS API」一文。

請使用 CryptoKeyVersions.decapsulate 方法。

curl "https://cloudkms.googleapis.com/v1/projects/PROJECT_ID/locations/LOCATION/keyRings/KEY_RING/cryptoKeys/KEY_NAME/cryptoKeyVersions/KEY_VERSION:decapsulate" \
  --request "POST" \
  --header "authorization: Bearer TOKEN" \
  --header "content-type: application/json" \
  --data '{"ciphertext": "CIPHERTEXT"}'

更改下列內容:

  • PROJECT_ID:包含金鑰環的專案 ID。
  • LOCATION:金鑰環的 Cloud KMS 位置。
  • KEY_RING:金鑰所屬金鑰環的名稱。
  • KEY_NAME:用於加密的金鑰名稱。
  • KEY_VERSION:用於加密的金鑰版本 ID
  • CIPHERTEXT:您要解封裝的 Base64 編碼密文。