KEM을 사용하여 캡슐화 및 캡슐 해제

이 문서에서는 Cloud KMS 키와 함께 키 캡슐화 메커니즘 (KEM)을 사용하여 공유 보안 비밀을 설정하는 방법을 설명합니다.

캡슐화는 KEM 키 쌍의 공개 키를 사용하고, 캡슐 해제는 키 쌍의 비공개 키를 사용합니다. Cloud KMS를 사용하면 공개 키를 검색할 수 있으며, 이 공개 키를 표준 라이브러리와 함께 사용하여 공유 보안 비밀을 캡슐화할 수 있습니다. 공유 보안 비밀을 캡슐 해제하려면 Cloud KMS 캡슐 해제 메서드를 사용합니다. Cloud KMS 외부에서는 비공개 키 자료를 사용할 수 없습니다.

시작하기 전에

  • 이 문서에서는 명령줄에서 실행되는 예를 제공합니다. 예시를 간단히 사용하려면 Cloud Shell을 사용하세요. 암호화 예시는 Cloud Shell에 사전 설치된 OpenSSL을 사용합니다. 그렇지 않으면 머신에 OpenSSL을 설치합니다.
  • 키 용도KEY_ENCAPSULATIONKEM 키를 만듭니다. 키 용도 KEY_ENCAPSULATION를 지원하는 알고리즘을 확인하려면 키 캡슐화 알고리즘을 참고하세요.

키에 대한 권한 부여

  • 보안 비밀을 캡슐화하기 위해 공개 키를 검색해야 하는 각 사용자 또는 주체에게 키에 대한 roles/cloudkms.publicKeyViewer 역할을 부여합니다.
  • 이 키로 보안 비밀을 캡슐 해제해야 하는 각 사용자 또는 주 구성원에게 키에 대한 'roles/cloudkms.decapsulator' 역할을 부여합니다.

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입니다.

공개 키 다시 포맷

encapsulate 명령어에는 공개 키가 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 인코딩 암호문