Encrypting and decrypting data with an asymmetric key

This topic provides information about creating and using a key for asymmetric encryption using an RSA key. If you want to use asymmetric keys for creating and validating signatures, see Creating and validating digital signatures. If you want to use symmetric keys for encryption and decryption, see Encrypting and decrypting data.

Asymmetric encryption uses the public key portion of the asymmetric key and decryption uses the private key portion of the key. Cloud Key Management Service provides functionality to retrieve the public key and functionality to decrypt ciphertext that was encrypted with the public key. Cloud KMS does not allow direct access to the private key.

Before you begin

  • This topic 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.

  • Create an asymmetric key with key purpose of ASYMMETRIC_DECRYPT. To see which algorithms are supported for key purpose ASYMMETRIC_DECRYPT, see Asymmetric encryption algorithms. You cannot follow this procedure with a key with purpose of ASYMMETRIC_SIGN.

  • If you are going to use the command line, install OpenSSL if you do not already have it. If you use Cloud Shell, OpenSSL is already installed.

  • macOS users: The version of OpenSSL installed on macOS does not support the flags used to decrypt data in this topic. To follow these steps on macOS, install OpenSSL from Homebrew.

Access control to the key

  • For a user or service that will retrieve the public key, grant the cloudkms.cryptoKeyVersions.viewPublicKey permission on the asymmetric key. The public key is required for encrypting data.

  • For a user or service that will decrypt data that was encrypted with the public key, grant the cloudkms.cryptoKeyVersions.useToDecrypt permission on the asymmetric key.

Learn about permissions and roles in Cloud KMS at Permissions and Roles.

Encrypt data

If you don't already have the public key, retrieve the public key.

The plaintext file you want to encrypt must not be larger than 64 KiB (65536 bytes).

Command-line

This example assumes the public key is in a file named rsa-decrypt-key.pub.

  1. Click the Activate Cloud Shell button at the top of the console window. Activate Cloud Shell A Cloud Shell session opens inside a new frame at the bottom of the console and displays a command-line prompt. It can take a few seconds for the session to be initialized. Cloud Shell session

  2. At the Cloud Shell command-line prompt, create a plaintext file.

    echo "my secret message" > ~/my-secret-file
    
  3. Use OpenSSL to encrypt the file.

    openssl pkeyutl -in ~/my-secret-file \
      -encrypt -pubin \
      -inkey ~/rsa-decrypt-key.pub \
      -pkeyopt rsa_padding_mode:oaep \
      -pkeyopt rsa_oaep_md:sha256 \
      -pkeyopt rsa_mgf1_md:sha256 > ~/my-secret-file.enc
    

Go

import (
	"context"
	"crypto/rand"
	"crypto/rsa"
	"crypto/sha256"
	"crypto/x509"
	"encoding/pem"
	"fmt"

	cloudkms "cloud.google.com/go/kms/apiv1"
	kmspb "google.golang.org/genproto/googleapis/cloud/kms/v1"
)

// encryptRSA will encrypt data locally using an 'RSA_DECRYPT_OAEP_2048_SHA256'
// public key retrieved from Cloud KMS.
func encryptRSA(name string, plaintext []byte) ([]byte, error) {
	// name: "projects/PROJECT_ID/locations/global/keyRings/RING_ID/cryptoKeys/KEY_ID/cryptoKeyVersions/1"
	// plaintext := []byte("Sample message")
	ctx := context.Background()
	client, err := cloudkms.NewKeyManagementClient(ctx)
	if err != nil {
		return nil, fmt.Errorf("cloudkms.NewKeyManagementClient: %v", err)
	}

	// Retrieve the public key from KMS.
	response, err := client.GetPublicKey(ctx, &kmspb.GetPublicKeyRequest{Name: name})
	if err != nil {
		return nil, fmt.Errorf("GetPublicKey: %v", err)
	}
	// Parse the key.
	block, _ := pem.Decode([]byte(response.Pem))
	abstractKey, err := x509.ParsePKIXPublicKey(block.Bytes)
	if err != nil {
		return nil, fmt.Errorf("x509.ParsePKIXPublicKey: %+v", err)
	}
	rsaKey, ok := abstractKey.(*rsa.PublicKey)
	if !ok {
		return nil, fmt.Errorf("key %q is not RSA", name)
	}
	// Encrypt data using the RSA public key.
	ciphertext, err := rsa.EncryptOAEP(sha256.New(), rand.Reader, rsaKey, plaintext, nil)
	if err != nil {
		return nil, fmt.Errorf("rsa.EncryptOAEP: %v", err)
	}
	return ciphertext, nil
}

Java

/**
 * Encrypt data locally using an 'RSA_DECRYPT_OAEP_2048_SHA256' public key
 * retrieved from Cloud KMS
 *
 * Example keyName:
 *   "projects/PROJECT_ID/locations/global/keyRings/RING_ID/cryptoKeys/KEY_ID/cryptoKeyVersions/1"
 */
public static byte[] encryptRSA(String keyName, byte[] plaintext)
    throws IOException, GeneralSecurityException {
  // Create the Cloud KMS client.
  try (KeyManagementServiceClient client = KeyManagementServiceClient.create()) {
    // Get the public key
    com.google.cloud.kms.v1.PublicKey pub = client.getPublicKey(keyName);
    String pemKey = pub.getPem();
    pemKey = pemKey.replaceFirst("-----BEGIN PUBLIC KEY-----", "");
    pemKey = pemKey.replaceFirst("-----END PUBLIC KEY-----", "");
    pemKey = pemKey.replaceAll("\\s", "");
    byte[] derKey = BaseEncoding.base64().decode(pemKey);
    X509EncodedKeySpec keySpec = new X509EncodedKeySpec(derKey);
    PublicKey rsaKey = KeyFactory.getInstance("RSA").generatePublic(keySpec);

    // Encrypt plaintext for the 'RSA_DECRYPT_OAEP_2048_SHA256' key.
    // For other key algorithms:
    // https://docs.oracle.com/javase/7/docs/api/javax/crypto/Cipher.html
    Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
    OAEPParameterSpec oaepParams = new OAEPParameterSpec(
        "SHA-256", "MGF1", MGF1ParameterSpec.SHA256, PSource.PSpecified.DEFAULT);
    cipher.init(Cipher.ENCRYPT_MODE, rsaKey, oaepParams);

    return cipher.doFinal(plaintext);
  }
}

Node.js

async function asymmetricEncrypt(
  projectId = 'your-project-id', // Your GCP projectId
  keyRingId = 'my-key-ring', // Name of the crypto key's key ring
  cryptoKeyId = 'my-key', // Name of the crypto key, e.g. "my-key"
  cryptoKeyVersionId = '1', // Version of the crypto key to use
  plaintext = 'my data to encrypt' // Plaintext data to encrypt
) {
  // Import the library and create a client
  const kms = require('@google-cloud/kms');
  const client = new kms.KeyManagementServiceClient();

  // The location of the crypto key's key ring, e.g. "global"
  const locationId = 'global';

  // Construct the cyrpto key version ID
  const name = client.cryptoKeyVersionPath(
    projectId,
    locationId,
    keyRingId,
    cryptoKeyId,
    cryptoKeyVersionId
  );

  // Get public key from Cloud KMS
  const [publicKey] = await client.getPublicKey({name: name});

  // Import and setup crypto
  const crypto = require('crypto');
  const plaintextBuffer = Buffer.from(plaintext);

  // Encrypt plaintext locally using the public key. This example uses a key
  // that was configured with sha256 hash with OAEP padding. Update these values
  // to match the Cloud KMS key.
  //
  // NOTE: In Node < 12, this function does not properly consume the OAEP
  // padding and thus produces invalid ciphertext. If you are using Node to do
  // public key encryption, please use version 12+.
  const encryptedBuffer = crypto.publicEncrypt(
    {
      key: publicKey.pem,
      oaepHash: 'sha256',
      padding: crypto.constants.RSA_PKCS1_OAEP_PADDING,
    },
    plaintextBuffer
  );

  // Example of how to display ciphertext. Because the ciphertext is in a binary
  // format, you need to encode the output before printing it to a console or
  // displaying it on a screen.
  const encoded = encryptedBuffer.toString('base64');
  console.log(`Encrypted ciphertext: ${encoded}`);

  // Return the ciphertext buffer
  return encryptedBuffer;
}

Python

def encrypt_rsa(plaintext, key_name):
    """
    Encrypt the input plaintext (bytes) locally using an
    'RSA_DECRYPT_OAEP_2048_SHA256' public key retrieved from Cloud KMS

    Example key_name:
      "projects/PROJECT_ID/locations/global/keyRings/RING_ID/cryptoKeys\
              /KEY_ID/cryptoKeyVersions/1"

    Requires:
      cryptography.hazmat.primitives.asymmetric.padding
      cryptography.hazmat.primitives.hashes
    """
    # get the public key
    client = kms_v1.KeyManagementServiceClient()
    response = client.get_public_key(key_name)
    key_txt = response.pem.encode('ascii')
    public_key = serialization.load_pem_public_key(key_txt, default_backend())

    # encrypt plaintext
    pad = padding.OAEP(mgf=padding.MGF1(algorithm=hashes.SHA256()),
                       algorithm=hashes.SHA256(),
                       label=None)
    return public_key.encrypt(plaintext, pad)

Decrypt data

Use Cloud KMS to perform the decryption.

Command-line

gcloud kms asymmetric-decrypt \
  --location location \
  --keyring keyring-name \
  --key key-name \
  --version key-version \
  --ciphertext-file ~/my-secret-file.enc \
  --plaintext-file ~/my-secret-file.dec

Display the contents of the decrypted file.

cat ~/my-secret-file.dec

You should see the same text that you created for your plaintext file.

my secret message

API

Use the CryptoKeyVersions.asymmetricDecrypt method.

  • Set the request's ciphertext value to the ciphertext that was created when you encrypted the plaintext.

The CryptoKeyVersions.asymmetricDecrypt response contains the decrypted data.

Go

import (
	"context"
	"fmt"

	cloudkms "cloud.google.com/go/kms/apiv1"
	kmspb "google.golang.org/genproto/googleapis/cloud/kms/v1"
)

// decryptRSA will attempt to decrypt a given ciphertext with an
// 'RSA_DECRYPT_OAEP_2048_SHA256' private key stored on Cloud KMS.
func decryptRSA(name string, ciphertext []byte) ([]byte, error) {
	// name := "projects/PROJECT_ID/locations/global/keyRings/RING_ID/cryptoKeys/KEY_ID/cryptoKeyVersions/1"
	// ciphertext, err := encryptRSA(rsaDecryptPath, []byte("Sample message"))
	// if err != nil {
	//   return nil, fmt.Errorf("encryptRSA: %v", err)
	// }
	ctx := context.Background()
	client, err := cloudkms.NewKeyManagementClient(ctx)
	if err != nil {
		return nil, fmt.Errorf("cloudkms.NewKeyManagementClient: %v", err)
	}

	// Build the request.
	req := &kmspb.AsymmetricDecryptRequest{
		Name:       name,
		Ciphertext: ciphertext,
	}
	// Call the API.
	response, err := client.AsymmetricDecrypt(ctx, req)
	if err != nil {
		return nil, fmt.Errorf("AsymmetricDecrypt: %v", err)
	}
	return response.Plaintext, nil
}

Java

/**
 * Decrypt a given ciphertext using an 'RSA_DECRYPT_OAEP_2048_SHA256' private key
 * stored on Cloud KMS
 *
 * Example keyName:
 *   "projects/PROJECT_ID/locations/global/keyRings/RING_ID/cryptoKeys/KEY_ID/cryptoKeyVersions/1"
 */
public static byte[] decryptRSA(String keyName, byte[] ciphertext) throws IOException {
  // Create the Cloud KMS client.
  try (KeyManagementServiceClient client = KeyManagementServiceClient.create()) {
    AsymmetricDecryptResponse response = client.asymmetricDecrypt(
        keyName, ByteString.copyFrom(ciphertext));
    return response.getPlaintext().toByteArray();
  }
}

Node.js

async function asymmetricDecrypt(
  projectId = 'your-project-id', // Your GCP projectId
  keyRingId = 'my-key-ring', // Name of the crypto key's key ring
  cryptoKeyId = 'my-key', // Name of the crypto key, e.g. "my-key"
  cryptoKeyVersionId = '1', // Version of the crypto key to use
  ciphertextBuffer = '...' // Buffer containing ciphertext to decrypt
) {
  // Import the library and create a client
  const kms = require('@google-cloud/kms');
  const client = new kms.KeyManagementServiceClient();

  // The location of the crypto key's key ring, e.g. "global"
  const locationId = 'global';

  // Construct the cyrpto key version ID
  const name = client.cryptoKeyVersionPath(
    projectId,
    locationId,
    keyRingId,
    cryptoKeyId,
    cryptoKeyVersionId
  );

  // Decrypt plaintext using Cloud KMS
  //
  // NOTE: The ciphertext must be properly formatted. In Node < 12, the
  // crypto.publicEncrypt() function does not properly consume the OAEP padding
  // and thus produces invalid ciphertext. If you are using Node to do public
  // key encryption, please use version 12+.
  const [result] = await client.asymmetricDecrypt({
    name: name,
    ciphertext: ciphertextBuffer,
  });
  const plaintext = result.plaintext.toString('utf8');

  // Example of printing results
  console.log(`Decrypted plaintext: ${plaintext}`);

  return plaintext;
}

Python

def decrypt_rsa(ciphertext, key_name):
    """
    Decrypt the input ciphertext (bytes) using an
    'RSA_DECRYPT_OAEP_2048_SHA256' private key stored on Cloud KMS

    Example key_name:
      "projects/PROJECT_ID/locations/global/keyRings/RING_ID/cryptoKeys\
              /KEY_ID/cryptoKeyVersions/1"
    """

    client = kms_v1.KeyManagementServiceClient()
    response = client.asymmetric_decrypt(key_name, ciphertext)
    return response.plaintext

Troubleshooting

incorrect key purpose: ASYMMETRIC_SIGN

You can only decrypt data with a key with key purpose ASYMMETRIC_DECRYPT.

invalid parameter when decrypting on macOS

The version of OpenSSL installed on macOS does not support the flags used to decrypt data in this topic. To follow these steps on macOS, install OpenSSL from Homebrew.

Was this page helpful? Let us know how we did:

Send feedback about...

Cloud KMS Documentation