הצפנה ופענוח באמצעות KEM

במסמך הזה מתואר שימוש במנגנוני אריזת מפתחות (KEM) עם מפתחות Cloud KMS כדי ליצור סודות משותפים.

ההצפנה נעשית באמצעות המפתח הציבורי של זוג מפתחות KEM, והפענוח נעשה באמצעות המפתח הפרטי של זוג המפתחות. בעזרת Cloud KMS אפשר לאחזר את המפתח הציבורי, ואז להשתמש בו עם ספריות רגילות כדי להצפין את הסוד המשותף. כדי לבטל את האריזה של הסוד המשותף, משתמשים בשיטות ביטול האריזה של Cloud KMS. אי אפשר להשתמש בחומר המפתח הפרטי מחוץ ל-Cloud KMS.

לפני שמתחילים

  • במסמך הזה מובאות דוגמאות להרצה בשורת הפקודה. כדי לפשט את השימוש בדוגמאות, אפשר להשתמש ב-Cloud Shell. בדוגמה להצפנה נעשה שימוש ב-OpenSSL, שמותקן מראש ב-Cloud Shell. אחרת, צריך להתקין את OpenSSL במחשב.
  • יוצרים מפתח KEM עם key purposeKEY_ENCAPSULATION. כדי לראות אילו אלגוריתמים נתמכים למטרת המפתח 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, אפשר להמשיך לשלב Encapsulate.

משתמשים בפקודה הבאה כדי להמיר את המפתח הציבורי למפתח 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.

הוספת אנקפסולציה

כדי ליצור סוד לשימוש עם טוקן צרכן ומידע מוצפן (ciphertext), אפשר להשתמש בפקודה הבאה:

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: מזהה הפרויקט שמכיל את מחזיק המפתחות.
  • LOCATION: המיקום ב-Cloud KMS שבו נמצא אוסף המפתחות.
  • KEY_RING: השם של אוסף המפתחות שמכיל את המפתח.
  • KEY_NAME: השם של המפתח שרוצים להשתמש בו להצפנה.
  • KEY_VERSION: המזהה של גרסת המפתח שבה רוצים להשתמש להצפנה
  • CIPHERTEXT: הטקסט המוצפן בקידוד base64 שרוצים לבטל את האנקפסולציה שלו.