Client-side encryption with Tink and Cloud KMS

This topic describes how to encrypt data locally and upload it toCloud Storage with Tink and Cloud Key Management Service (Cloud KMS). Tink is anopen source cryptography library written by cryptographers and securityengineers at Google.

Overview

Client-side encryption is any encryption performed prior to sending your data tothe cloud. When using client-side encryption, you're responsible for creatingand managing your encryption keys and encrypting your data before sending it tothe cloud.

In this topic, you implement client-side envelope encryption with Tink using anencryption key in Cloud KMS.

You can find a Terraform-based blueprint version of this tutorial in thekms-solutions GitHub repository.

Before you begin

  1. Create a symmetric Cloud KMS encryption key for encryption. Takenote of the URI of the key. You need it later.
  2. Install Tink for use with Cloud KMS.
  3. Create a bucket in Cloud Storage to upload your encrypted data.

Required roles

To ensure that your service account has the necessary permissions to use Cloud KMS keys with Tink, ask your administrator to grant your service account theCloud KMS CryptoKey Encrypter/Decrypter (roles/cloudkms.cryptoKeyEncrypterDecrypter) IAM role on your key.Important: You must grant this role to your service account,not to your user account. Failure to grant the role to the correct principal might result in permission errors. For more information about granting roles, seeManage access to projects, folders, and organizations.

Your administrator might also be able to give your service account the required permissions throughcustom roles or otherpredefined roles.

Envelope encryption with Tink

Inenvelope encryption, the Cloud KMS key acts as a key encryptionkey (KEK). That is, it's used to encrypt data encryption keys (DEK) which inturn are used to encrypt actual data.

After creating a KEK in Cloud KMS, to encrypt each message you must:

  1. Generate a data encryption key (DEK) locally.
  2. Use the DEK locally to encrypt the message.
  3. Use Cloud KMS to encrypt (wrap) the DEK with the KEK.
  4. Store the encrypted data and the wrapped DEK.

You don't need to implement this envelope encryption process from scratch whenyou use Tink.

To use Tink for envelope encryption, you provide Tink with a key URI andcredentials. The key URI points to your KEK in Cloud KMS, and thecredentials let Tink use the KEK. Tink generates the DEK, encrypts the data,wraps the DEK, and then returns a single ciphertext with the encrypted data andwrapped DEK.

Tink supports envelope encryption in Python, Java, C++, and Go using theAuthenticated Encryption with Associated Data (AEAD) primitive.

Connect Tink and Cloud KMS

To encrypt the DEKs generated by Tink with your KEK in Cloud KMS,you need to get the URI of your KEK. In Cloud KMS, the KEK URI has thefollowing format:

gcp-kms://projects/PROJECT_ID/locations/LOCATION/keyRings/KEY_RING/cryptoKeys/KEY_NAME/cryptoKeyVersions/KEY_VERSION

SeeGetting a Cloud KMS resource ID for details on how to getthe path to your key.

Initialize Tink and encrypt data

Tink uses primitives—cryptographic building blocks that manage the detailsof their underlying algorithms—so you can perform tasks safely. Eachprimitive offers an API that handles a specific task. Here, we're using AEAD, sowe use the Tink AEAD primitive.

Python

Python

To learn how to install and use the client library for Cloud KMS, seeCloud KMS client libraries.

To authenticate to Cloud KMS, set up Application Default Credentials. For more information, seeSet up authentication for a local development environment.

"""A command-line utility for performing file encryption using GCS.It is inteded for use with small files, utilizes envelope encryption andfacilitates ciphertexts stored in GCS."""fromabslimportappfromabslimportflagsfromabslimportloggingfromgoogle.cloudimportstorageimporttinkfromtinkimportaeadfromtink.integrationimportgcpkmsFLAGS=flags.FLAGSflags.DEFINE_enum('mode',None,['encrypt','decrypt'],'The operation to perform.')flags.DEFINE_string('kek_uri',None,'The Cloud KMS URI of the key encryption key.')flags.DEFINE_string('gcp_credential_path',None,'Path to the GCP credentials JSON file.')flags.DEFINE_string('gcp_project_id',None,'The ID of the GCP project hosting the GCS blobs.')flags.DEFINE_string('local_path',None,'Path to the local file.')flags.DEFINE_string('gcs_blob_path',None,'Path to the GCS blob.')_GCS_PATH_PREFIX='gs://'defmain(argv):delargv# Unused.# Initialise Tinkaead.register()try:# Read the GCP credentials and setup clientclient=gcpkms.GcpKmsClient(FLAGS.kek_uri,FLAGS.gcp_credential_path)excepttink.TinkErrorase:logging.exception('Error creating GCP KMS client:%s',e)return1# Create envelope AEAD primitive using AES256 GCM for encrypting the datatry:remote_aead=client.get_aead(FLAGS.kek_uri)env_aead=aead.KmsEnvelopeAead(aead.aead_key_templates.AES256_GCM,remote_aead)excepttink.TinkErrorase:logging.exception('Error creating primitive:%s',e)return1storage_client=storage.Client.from_service_account_json(FLAGS.gcp_credential_path)try:bucket_name,object_name=_get_bucket_and_object(FLAGS.gcs_blob_path)exceptValueErrorase:logging.exception('Error parsing GCS blob path:%s',e)return1bucket=storage_client.bucket(bucket_name)blob=bucket.blob(object_name)associated_data=FLAGS.gcs_blob_path.encode('utf-8')ifFLAGS.mode=='encrypt':withopen(FLAGS.local_path,'rb')asinput_file:output_data=env_aead.encrypt(input_file.read(),associated_data)blob.upload_from_string(output_data)elifFLAGS.mode=='decrypt':ciphertext=blob.download_as_bytes()withopen(FLAGS.local_path,'wb')asoutput_file:output_file.write(env_aead.decrypt(ciphertext,associated_data))else:logging.error('Unsupported mode%s. Please choose "encrypt" or "decrypt".',FLAGS.mode,)return1def_get_bucket_and_object(gcs_blob_path):"""Extract bucket and object name from a GCS blob path.  Args:    gcs_blob_path: path to a GCS blob  Returns:    The bucket and object name of the GCS blob  Raises:    ValueError: If gcs_blob_path parsing fails.  """ifnotgcs_blob_path.startswith(_GCS_PATH_PREFIX):raiseValueError(f'GCS blob paths must start with gs://, got{gcs_blob_path}')path=gcs_blob_path[len(_GCS_PATH_PREFIX):]parts=path.split('/',1)iflen(parts) <2:raiseValueError('GCS blob paths must be in format gs://bucket-name/object-name, 'f'got{gcs_blob_path}')returnparts[0],parts[1]if__name__=='__main__':flags.mark_flags_as_required(['mode','kek_uri','gcp_credential_path','gcp_project_id','local_path','gcs_blob_path'])app.run(main)

Java

Java

To learn how to install and use the client library for Cloud KMS, seeCloud KMS client libraries.

To authenticate to Cloud KMS, set up Application Default Credentials. For more information, seeSet up authentication for a local development environment.

packagegcs;import staticjava.nio.charset.StandardCharsets.UTF_8;importcom.google.auth.oauth2.GoogleCredentials;importcom.google.cloud.storage.BlobId;importcom.google.cloud.storage.BlobInfo;importcom.google.cloud.storage.Storage;importcom.google.cloud.storage.StorageOptions;importcom.google.crypto.tink.Aead;importcom.google.crypto.tink.KmsClient;importcom.google.crypto.tink.aead.AeadConfig;importcom.google.crypto.tink.aead.KmsEnvelopeAead;importcom.google.crypto.tink.aead.PredefinedAeadParameters;importcom.google.crypto.tink.integration.gcpkms.GcpKmsClient;importjava.io.File;importjava.io.FileInputStream;importjava.io.FileOutputStream;importjava.nio.file.Files;importjava.nio.file.Paths;importjava.security.GeneralSecurityException;importjava.util.Arrays;/** * A command-line utility for encrypting small files with envelope encryption and uploading the * results to GCS. * * <p>The CLI takes the following required arguments: * * <ul> *   <li>mode: "encrypt" or "decrypt" to indicate if you want to encrypt or decrypt. *   <li>kek-uri: The URI for the Cloud KMS key to be used for envelope encryption. *   <li>gcp-credential-file: Name of the file with the GCP credentials (in JSON format) that can *       access the Cloud KMS key and the GCS input/output blobs. *   <li>gcp-project-id: The ID of the GCP project hosting the GCS blobs that you want to encrypt or *       decrypt. * </ul> * * <p>When mode is "encrypt", it takes the following additional arguments: * * <ul> *   <li>local-input-file: Read the plaintext from this local file. *   <li>gcs-output-blob: Write the encryption result to this blob in GCS. The encryption result is *       bound to the location of this blob. That is, if you rename or move it to a different *       bucket, decryption will fail. * </ul> * * <p>When mode is "decrypt", it takes the following additional arguments: * * <ul> *   <li>gcs-input-blob: Read the ciphertext from this blob in GCS. *   <li>local-output-file: Write the decryption result to this local file. */publicfinalclassGcsEnvelopeAeadExample{privatestaticfinalStringMODE_ENCRYPT="encrypt";privatestaticfinalStringMODE_DECRYPT="decrypt";privatestaticfinalStringGCS_PATH_PREFIX="gs://";publicstaticvoidmain(String[]args)throwsException{if(args.length!=6){System.err.printf("Expected 6 parameters, got %d\n",args.length);System.err.println("Usage: java GcsEnvelopeAeadExample encrypt/decrypt kek-uri gcp-credential-file"+" gcp-project-id input-file output-file");System.exit(1);}Stringmode=args[0];StringkekUri=args[1];StringgcpCredentialFilename=args[2];StringgcpProjectId=args[3];// Initialise Tink: register all AEAD key types with the Tink runtimeAeadConfig.register();// Read the GCP credentials and create a remote AEAD object.AeadremoteAead=null;try{KmsClientkmsClient=newGcpKmsClient().withCredentials(gcpCredentialFilename);remoteAead=kmsClient.getAead(kekUri);}catch(GeneralSecurityExceptionex){System.err.println("Error initializing GCP client: "+ex);System.exit(1);}// Create envelope AEAD primitive using AES256 GCM for encrypting the dataAeadaead=KmsEnvelopeAead.create(PredefinedAeadParameters.AES256_GCM,remoteAead);GoogleCredentialscredentials=GoogleCredentials.fromStream(newFileInputStream(gcpCredentialFilename)).createScoped(Arrays.asList("https://www.googleapis.com/auth/cloud-platform"));Storagestorage=StorageOptions.newBuilder().setProjectId(gcpProjectId).setCredentials(credentials).build().getService();// Use the primitive to encrypt/decrypt files.if(MODE_ENCRYPT.equals(mode)){// Encrypt the local filebyte[]input=Files.readAllBytes(Paths.get(args[4]));StringgcsBlobPath=args[5];// This will bind the encryption to the location of the GCS blob. That if, if you rename or// move the blob to a different bucket, decryption will fail.// See https://developers.google.com/tink/aead#associated_data.byte[]associatedData=gcsBlobPath.getBytes(UTF_8);byte[]ciphertext=aead.encrypt(input,associatedData);// Upload to GCSStringbucketName=getBucketName(gcsBlobPath);StringobjectName=getObjectName(gcsBlobPath);BlobIdblobId=BlobId.of(bucketName,objectName);BlobInfoblobInfo=BlobInfo.newBuilder(blobId).build();storage.create(blobInfo,ciphertext);}elseif(MODE_DECRYPT.equals(mode)){// Download the GCS blobStringgcsBlobPath=args[4];StringbucketName=getBucketName(gcsBlobPath);StringobjectName=getObjectName(gcsBlobPath);byte[]input=storage.readAllBytes(bucketName,objectName);// Decrypt to a local filebyte[]associatedData=gcsBlobPath.getBytes(UTF_8);byte[]plaintext=aead.decrypt(input,associatedData);FileoutputFile=newFile(args[5]);try(FileOutputStreamstream=newFileOutputStream(outputFile)){stream.write(plaintext);}}else{System.err.println("The first argument must be either encrypt or decrypt, got: "+mode);System.exit(1);}System.exit(0);}privatestaticStringgetBucketName(StringgcsBlobPath){if(!gcsBlobPath.startsWith(GCS_PATH_PREFIX)){thrownewIllegalArgumentException("GCS blob paths must start with gs://, got "+gcsBlobPath);}StringbucketAndObjectName=gcsBlobPath.substring(GCS_PATH_PREFIX.length());intfirstSlash=bucketAndObjectName.indexOf("/");if(firstSlash==-1){thrownewIllegalArgumentException("GCS blob paths must have format gs://my-bucket-name/my-object-name, got "+gcsBlobPath);}returnbucketAndObjectName.substring(0,firstSlash);}privatestaticStringgetObjectName(StringgcsBlobPath){if(!gcsBlobPath.startsWith(GCS_PATH_PREFIX)){thrownewIllegalArgumentException("GCS blob paths must start with gs://, got "+gcsBlobPath);}StringbucketAndObjectName=gcsBlobPath.substring(GCS_PATH_PREFIX.length());intfirstSlash=bucketAndObjectName.indexOf("/");if(firstSlash==-1){thrownewIllegalArgumentException("GCS blob paths must have format gs://my-bucket-name/my-object-name, got "+gcsBlobPath);}returnbucketAndObjectName.substring(firstSlash+1);}privateGcsEnvelopeAeadExample(){}}

For more information about the primitives and interfaces supported by Tink, seetheGet started page for Tink.

What's next?

Except as otherwise noted, the content of this page is licensed under theCreative Commons Attribution 4.0 License, and code samples are licensed under theApache 2.0 License. For details, see theGoogle Developers Site Policies. Java is a registered trademark of Oracle and/or its affiliates.

Last updated 2026-02-19 UTC.