This guide shows you how to import a cryptographic key into Cloud Key Management Service as a new key version using a quantum-safe import method. This approach helps protect the key during transit against "harvest now, decrypt later" (HNDL) attacks by future quantum computers.
Quantum-safe key import uses standard post-quantum cryptography (PQC) tools including key encapsulation mechanisms (KEMs) and hybrid public key encryption (HPKE) to protect your key while in transit.
Quantum-safe key import is supported for software-backed keys (SOFTWARE
protection level).
Before you begin
Before you can import a key, you need to prepare the project, the local system, and the key material itself.
Prepare the project
-
In the Cloud de Confiance console, on the project selector page, select or create a Cloud de Confiance project.
Roles required to select or create a project
- Select a project: Selecting a project doesn't require a specific IAM role—you can select any project that you've been granted a role on.
-
Create a project: To create a project, you need the Project Creator role
(
roles/resourcemanager.projectCreator), which contains theresourcemanager.projects.createpermission. Learn how to grant roles.
-
Verify that billing is enabled for your Cloud de Confiance project.
Enable the required API.
Roles required to enable APIs
To enable APIs, you need the
serviceusage.services.enablepermission. If you created the project, then you likely already have this permission through the Owner role (roles/owner). Otherwise, you can get this permission through the Service Usage Admin role (roles/serviceusage.serviceUsageAdmin). Learn how to grant roles.-
Install the Google Cloud CLI.
-
Configure the gcloud CLI to use your federated identity.
For more information, see Sign in to the gcloud CLI with your federated identity.
-
To initialize the gcloud CLI, run the following command:
gcloud init
Required roles
To get the permissions that you need to import a key, ask your administrator to grant you the following IAM roles on the key ring:
-
To import into existing keys only:
Cloud KMS Importer (
roles/cloudkms.importer) -
To import into new keys:
Cloud KMS Admin (
roles/cloudkms.admin)
For more information about granting roles, see Manage access to projects, folders, and organizations.
You might also be able to get the required permissions through custom roles or other predefined roles.
Prepare the local system
You need a cryptographic library on your local system that supports post-quantum cryptography (PQC) tools including key encapsulation mechanisms (KEMs) and hybrid public key encryption (HPKE). You can use Tink, OpenSSL, or another cryptographic library that supports the following:
- Hybrid public key encryption (HPKE)
- One of the following KEM algorithms:
ML-KEM-768ML-KEM-1024X-WING(a hybrid ofML-KEM-768andX25519)
- The
HKDF-SHA256key derivation function (KDF) - Authenticated encryption with associated data (AEAD) using the
AES-256-GCMalgorithm
Prepare the key
Verify that your key's algorithm and length are
supported. All versions of a key must have the same protection level
(SOFTWARE).
Create the target key and key ring
When you import key material, it becomes a new key version on an existing key. This key is called the target key. The target key ring and target key must exist before you can import key material.
Follow these steps to create an empty software-backed key on a new key ring using the Google Cloud CLI or the Cloud de Confiance console.
Console
In the Cloud de Confiance console, go to the Key Management page.
Click Create key ring.
In the Key ring name field, enter the name for your key ring.
Under Location type, select a location type and location.
Click Create. The Create key page opens.
In the Key name field, enter the name for your key.
For Protection level, select Software.
For Key material, select Imported key and then click Continue. This prevents an initial key version from being created.
Set the Purpose and Algorithm for the key and then click Continue.
Optional: If you want this key to contain only imported key versions, select Restrict key versions to import only. This prevents you from accidentally creating new key versions in Cloud KMS.
Optional: For imported keys, automatic rotation is disabled by default. To enable automatic rotation, select a value from the Key rotation period field.
If you enable automatic rotation, new key versions will be generated in Cloud KMS, and the imported key version will no longer be the default key version after a rotation.
Click Create.
gcloud
To use Cloud KMS on the command line, first Install or upgrade to the latest version of Google Cloud CLI.
Create the target key ring. Choose a location that is compatible with the protection level that you want to use. For more information about supported locations, see Cloud KMS locations.
gcloud kms keyrings create KEY_RING \ --location LOCATION
You can learn more about creating key rings.
Create the target key using the
kms keys createcommand with the--skip-initial-version-creationflag. This creates a key with no initial key version so that your imported key material is version1. Use the--import-onlyflag to prevent Cloud KMS from generating key material for new key versions. With this flag set, new key versions for this key must be imported. Keys created as--import-onlymust be rotated manually.gcloud kms keys create KEY_NAME \ --location LOCATION \ --keyring KEY_RING \ --purpose PURPOSE \ --skip-initial-version-creation \ --import-only
Replace the following:
KEY_NAME: the name that you want to use for the key.LOCATION: the location of the key ring.KEY_RING: the key ring where you want to create the key.PURPOSE: the purpose that you want to use for the key.
API
These examples use curl as an HTTP client to demonstrate using the API. For more information about access control, see Accessing the Cloud KMS API.
Create a new key ring:
curl "https://cloudkms.googleapis.com/v1/projects/PROJECT_ID/locations/LOCATION/keyRings?keyRingId=KEY_RING" \ --request "POST" \ --header "authorization: Bearer TOKEN" \ --header "content-type: application/json" \ --header "x-goog-user-project: PROJECT_ID" \ --data "{}"See the
KeyRing.createAPI documentation for more information.Create an empty, import-only key:
curl "https://cloudkms.googleapis.com/v1/projects/PROJECT_ID/locations/LOCATION/keyRings/KEY_RING/cryptoKeys?cryptoKeyId=KEY_NAME&skipInitialVersionCreation=true" \ --request "POST" \ --header "authorization: Bearer TOKEN" \ --header "content-type: application/json" \ --header "x-goog-user-project: PROJECT_ID" \ --data "{"purpose":"PURPOSE", "importOnly": "true", "versionTemplate":{"protectionLevel":"PROTECTION_LEVEL","algorithm":"ALGORITHM"}}"See the
CryptoKey.createAPI documentation for more information.
The key ring and key now exist, but the key contains no key material, has no version, and is not active. Next, you create an import job.
Create the import job
An import job defines the characteristics of the keys it imports, including the protection level and the import method.
Quantum-safe key import is supported only for the SOFTWARE protection level.
Choose one of the following quantum-safe import methods:
HPKE_KEM_XWING_HKDF_SHA256_AES_256_GCMHPKE_KEM_ML_KEM_768_HKDF_SHA256_AES_256_GCMHPKE_KEM_ML_KEM_1024_HKDF_SHA256_AES_256_GCM
gcloud
Run the following command to create an import job with a quantum-safe import method:
gcloud kms import-jobs create IMPORT_JOB \
--location LOCATION \
--keyring KEY_RING \
--import-method IMPORT_METHOD \
--protection-level software
Replace the following:
IMPORT_JOB: a unique name to use for the import job.LOCATION: the location of the key ring where you created your target key.KEY_RING: the name of the key ring where you created your target key.IMPORT_METHOD: the quantum-safe import method that you want to use—for example,hpke-kem-xwing-hkdf-sha256-aes-256-gcm.
REST
Call the keyRings.importJobs.create method:
curl "https://cloudkms.googleapis.com/v1/projects/PROJECT_ID/locations/LOCATION/keyRings/KEY_RING/importJobs?import_job_id=IMPORT_JOB" \
--request "POST" \
--header "authorization: Bearer TOKEN" \
--header "content-type: application/json" \
--data '{"import_method": "IMPORT_METHOD", "protection_level": "SOFTWARE"}'
Replace the following:
PROJECT_ID: the identifier of your Cloud KMS project.LOCATION: the location of the key ring where you created your target key.KEY_RING: the name of the key ring where you created your target key.IMPORT_JOB: a unique name to use for the import job.TOKEN: the token to authenticate the request.IMPORT_METHOD: the quantum-safe import method that you want to use—for example,HPKE_KEM_XWING_HKDF_SHA256_AES_256_GCM.
Check the state of the import job
The initial state for an import job is PENDING_GENERATION. When the state is
ACTIVE, you can use it to import keys.
An import job expires after three days. If the import job is expired, you must create a new one.
You can check the status of an import job using the Google Cloud CLI, the Cloud de Confiance console, or the Cloud Key Management Service API.
Console
Go to the Key Management page in the Cloud de Confiance console.
Click the name of the key ring that contains your import job.
Click the Import Jobs tab at the top of the page.
The state will be visible under Status next to your import job's name.
gcloud
To use Cloud KMS on the command line, first Install or upgrade to the latest version of Google Cloud CLI.
When an import job is active, you can use it to import keys. This may take a few minutes. Use this command to verify that the import job is active. Use the location and keyring where you created the import job.
gcloud kms import-jobs describe IMPORT_JOB \ --location LOCATION \ --keyring KEY_RING \ --format="value(state)"
The output is similar to the following:
state: ACTIVE
Go
To run this code, first set up a Go development environment and install the Cloud KMS Go SDK.
Java
To run this code, first set up a Java development environment and install the Cloud KMS Java SDK.
Node.js
To run this code, first set up a Node.js development environment and install the Cloud KMS Node.js SDK.
Python
To run this code, first set up a Python development environment and install the Cloud KMS Python SDK.
API
These examples use curl as an HTTP client to demonstrate using the API. For more information about access control, see Accessing the Cloud KMS API.
To check the state of an import job, use the
ImportJobs.get
method:
curl "https://cloudkms.googleapis.com/v1/projects/PROJECT_ID/locations/LOCATION/keyRings/KEY_RING/importJobs/IMPORT_JOB_ID" \
--request "GET" \
--header "authorization: Bearer TOKEN"
As soon as the import job is active, you can make a request to import a key.
Retrieve the public wrapping key
After the import job is ACTIVE, retrieve the public key that's associated with
it. You will use this public key on your local system to wrap the key material
that you want to import.
gcloud
Run the following command to download the public key:
gcloud kms import-jobs describe IMPORT_JOB
--location LOCATION
--keyring KEY_RING
--format="value(publicKey.data)"
Replace the following:
IMPORT_JOB: the name of the import job.LOCATION: the location of the key ring where you created the import job.KEY_RING: the name of the key ring where you created the import job.
The public key is base64 encoded.
REST
- Call the
keyRings.importJobs.getmethod. - Retrieve the public key from the
publicKey.datafield of the response, and save it locally aspublic_key.data.
Prepare and wrap your key material
Use a supported external cryptographic library on your local system to wrap the key material using the retrieved public wrapping key.
The wrapping process must perform HPKE.Seal() (RFC 9180) to produce a wrapped
key. This completes the following steps:
- Encapsulate the retrieved public key to produce a shared secret and an encapsulation key.
- Derive an ephemeral symmetric key from the shared secret using HKDF-SHA256.
- Encrypt your key material with the ephemeral key using AES-256-GCM.
- Concatenate the encapsulation key and the key material encrypted as
ciphertext. This is the resulting wrapped key that you will use to import
the key. Save this as
wrapped_key.bin.
The following Go code sample demonstrates wrapping key material using the
tink-go library:
package main
import (
"bytes"
"encoding/base64"
"flag"
"fmt"
"log"
"google.golang.org/protobuf/proto"
"github.com/tink-crypto/tink-go/v2/hybrid"
"github.com/tink-crypto/tink-go/v2/keyset"
hpkepb "github.com/tink-crypto/tink-go/v2/proto/hpke_go_proto"
tinkpb "github.com/tink-crypto/tink-go/v2/proto/tink_go_proto"
)
var (
publicKeyB64Flag = flag.String("public_key", "", "Base64 encoded public key for wrapping.")
targetKeyB64Flag = flag.String("target_key", "", "Base64 encoded 32-byte target key to be wrapped.")
)
func main() {
flag.Parse()
if *publicKeyB64Flag == "" {
log.Fatal("-public_key is required")
}
if *targetKeyB64Flag == "" {
log.Fatal("-target_key is required")
}
pkBytes, err := base64.StdEncoding.DecodeString(*publicKeyB64Flag)
if err != nil {
log.Fatalf("failed to decode public key: %v", err)
}
targetKey, err := base64.StdEncoding.DecodeString(*targetKeyB64Flag)
if err != nil {
log.Fatalf("failed to decode target key: %v", err)
}
hpkePubKey := &hpkepb.HpkePublicKey{
Version: 0,
Params: &hpkepb.HpkeParams{
Kem: hpkepb.HpkeKem_ML_KEM768,
Kdf: hpkepb.HpkeKdf_HKDF_SHA256,
Aead: hpkepb.HpkeAead_AES_256_GCM,
},
PublicKey: pkBytes,
}
serializedPubKey, err := proto.Marshal(hpkePubKey)
if err != nil {
log.Fatalf("failed to marshal HPKE public key: %v", err)
}
ks := &tinkpb.Keyset{
PrimaryKeyId: 1,
Key: []*tinkpb.Keyset_Key{
{
KeyData: &tinkpb.KeyData{
TypeUrl: "type.googleapis.com/google.crypto.tink.HpkePublicKey",
Value: serializedPubKey,
KeyMaterialType: tinkpb.KeyData_ASYMMETRIC_PUBLIC,
},
Status: tinkpb.KeyStatusType_ENABLED,
KeyId: 1,
OutputPrefixType: tinkpb.OutputPrefixType_RAW,
},
},
}
serializedKeyset, err := proto.Marshal(ks)
if err != nil {
log.Fatalf("failed to marshal keyset: %v", err)
}
// Create a KeysetHandle and retrieve the HybridEncrypt primitive.
reader := keyset.NewBinaryReader(bytes.NewReader(serializedKeyset))
handle, err := keyset.ReadWithNoSecrets(reader)
if err != nil {
log.Fatalf("failed to create keyset handle: %v", err)
}
enc, err := hybrid.NewHybridEncrypt(handle)
if err != nil {
log.Fatalf("failed to create hybrid encrypt primitive: %v", err)
}
// Perform the wrapping operation. Tink's HPKE implementation handles the
// 'enc || ciphertext' concatenation automatically.
wrappedKey, err := enc.Encrypt(targetKey, nil)
if err != nil {
log.Fatalf("failed to wrap key: %v", err)
}
fmt.Printf("Final wrappedKey (base64):\n%s\n", base64.StdEncoding.EncodeToString(wrappedKey))
}
Save the output base64 string or decode it to a binary file:
bash
echo "BASE64_WRAPPED_KEY" | base64 --decode > wrapped_key.bin
Import the wrapped key
Import the prepared wrapped key as a new key version of your target key.
gcloud
Run the kms keys versions import command:
gcloud kms keys versions import \
--location LOCATION \
--keyring KEY_RING \
--key KEY_NAME \
--import-job IMPORT_JOB \
--algorithm ALGORITHM \
--wrapped-key-file wrapped_key.bin
Replace the following:
LOCATION: the location of the key ring that contains your target key.KEY_RING: the name of the key ring that contains your target key.KEY_NAME: the name of your target key.IMPORT_JOB: the name of your import job.ALGORITHM: the algorithm of the key material to be imported.
REST
Call the cryptoKeyVersions.import method:
curl "https://cloudkms.googleapis.com/v1/projects/PROJECT_ID/locations/LOCATION/keyRings/KEY_RING/cryptoKeys/KEY_NAME/cryptoKeyVersions:import" \
--request "POST" \
--header "authorization: Bearer TOKEN" \
--header "content-type: application/json" \
--data '{"importJob": "IMPORT_JOB", "algorithm": "ALGORITHM", "wrappedKey": "PATH_TO_WRAPPED_KEY"}'
Replace the following:
PROJECT_ID: the identifier of your Cloud KMS project.LOCATION: the location of the key ring that contains your target key.KEY_RING: the name of the key ring that contains your target key.KEY_NAME: the name of your target key.TOKEN: the token to authenticate the request.IMPORT_JOB: the identifier of the corresponding import job.ALGORITHM: the algorithm of the key material to be imported.PATH_TO_WRAPPED_KEY: the path to your manually-wrapped key in base64 format.
Check the state of the imported key version
The initial state for an imported key version is PENDING_IMPORT. When the
state is ENABLED, the key version has been imported successfully. If the
import fails, the status is IMPORT_FAILED.
You can check the status of an import request using the Google Cloud CLI, the Cloud de Confiance console, or the Cloud Key Management Service API.
Console
Open the Key Management page in the Cloud de Confiance console.
Click the name of the key ring that contains your import job.
Click the Import Jobs tab at the top of the page.
The state will be visible under Status next to your import job's name.
gcloud
To use Cloud KMS on the command line, first Install or upgrade to the latest version of Google Cloud CLI.
Use the versions list command to check the state. Use the same
location, target key ring, and target key that you created earlier in this
topic.
gcloud kms keys versions list \ --keyring KEY_RING \ --location LOCATION \ --key KEY_NAME
Go
To run this code, first set up a Go development environment and install the Cloud KMS Go SDK.
Java
To run this code, first set up a Java development environment and install the Cloud KMS Java SDK.
Node.js
To run this code, first set up a Node.js development environment and install the Cloud KMS Node.js SDK.
Python
To run this code, first set up a Python development environment and install the Cloud KMS Python SDK.
API
These examples use curl as an HTTP client to demonstrate using the API. For more information about access control, see Accessing the Cloud KMS API.
Call the ImportJob.get method and check the
[state][api_importjob_fields_state] field. If
state is PENDING_GENERATION, the import job is still being created.
Periodically recheck the state until it is ACTIVE.
After the initial key version is imported, the key's status changes to
ENABLED. For symmetric keys, you must set the imported key version as the
primary version before you can use the key.
Re-import a previously destroyed key
If you need to restore a previously imported key version that is in DESTROYED
or IMPORT_FAILED state back to ENABLED state, you can re-import the exact
same key material.
Re-importing a destroyed key version uses the same procedure as the initial
import, using either the original import job or a new import job (with the same
SOFTWARE protection level).