About client-side encryption Stay organized with collections Save and categorize content based on your preferences.
This page describes how to implement client-side encryption on Cloud SQL.
Overview
Client-side encryption is the act of encrypting data before writing it toCloud SQL. You can encrypt Cloud SQL data in a manner that only yourapplication can decrypt.
To enable client-side encryption you have the following options:
- Using an encryption key stored in the Cloud Key Management Service (Cloud KMS).
- Using an encryption key stored locally in your application.
In this topic, we describe how to use the first option, which provides the mostseamless key management option. We create an encryption key inCloud KMS and implementenvelope encryption usingTink, Google's open source cryptographiclibrary.
Why do you need client-side encryption?
You need client-side encryption if you want to protect Cloud SQL data atthe column level1. Imagine you have a table of names and credit cardnumbers. You want to grant a user access to this table, but you don't want themto view the credit card numbers. You can encrypt the numbers using client-sideencryption. As long as the user is not granted access to the encryption key inCloud KMS, they can't read the credit card information.
Create keys using Cloud KMS
Cloud KMS lets you create and manage keys on Google Cloud Platform.
Cloud KMS supports many different key types. For client-sideencryption, you need to create asymmetric key.
To give your application access to the key in Cloud KMS, you need togrant the service account that your application uses with thecloudkms.cryptoKeyEncrypterDecrypter
role. In gcloud, you use the followingcommand to do this:
gcloudkmskeysadd-iam-policy-bindingkey\--keyring=key-ring\--location=location\--member=serviceAccount:service-account-name@example.domain.com\--role=roles/cloudkms.cryptoKeyEncrypterDecrypter
While you can use the KMS key to directlyencrypt data, here we use a more flexible solutioncalledenvelope encryption. This allows us to encrypt messages longer than64KB, which is the maximum message size that the Cloud Key Management Service API can support.
Cloud KMS envelope encryption
In envelope encryption, the KMS key acts as a key encrypting key (KEK). That is,it is used to encrypt data encryption keys (DEK) which in turn are used toencrypt actual data.
After creating a KEK in Cloud KMS, to encrypt each message you need to:
- Generate a data encryption key (DEK) locally.
- Use this DEK locally to encrypt the message.
- Call Cloud KMS to encrypt (wrap) the DEK with the KEK.
- Store the encrypted data and the wrapped DEK.
Instead of implementing envelope encryption from scratch, in this topic we useTink.
Tink
Tink is a multi-language, cross-platform library that provides high-levelcryptographic APIs. To encrypt data with Tink's envelope encryption, you provideTink with a key URI pointing to your KEK in Cloud KMS, and credentialsthat allow Tink to use the KEK. Tink generates the DEK, encrypts the data, wrapsthe DEK and returns a single ciphertext with the encrypted data and wrapped DEK.
Tink supports envelope encryption in C++, Java, Go, and Python using the AEADAPI:
publicinterfaceAead{byte[]encrypt(finalbyte[]plaintext,finalbyte[]associatedData)throws…byte[]decrypt(finalbyte[]ciphertext,finalbyte[]associatedData)throws…}
Besides the normal message/ciphertext argument, the encrypt and decrypt methodssupport optional associated data. This argument can be used to tie theciphertext to a piece of data. For example, suppose you have a database with afielduser-id
and a fieldencrypted-medical-history
. In this case, the fielduser-id
should probably be used as associated data when encrypting the medicalhistory. This ensures that an attacker cannot move medical history from one userto another. It is also used to verify that you have the correct row of data whenyou run a query.
Samples
In this section, we'll walk through sample code for a voter information databasethat uses client-side encryption. The sample code shows how to:
- Create a database table and connection pool
- Set up Tink for envelope encryption
- Encrypt and decrypt data using Tink's envelope encryption with a KEK inCloud KMS
Before you begin
Create a Cloud SQL Instance by following theseinstructions.Note the connection string, database user, and database password that youcreate.
Create a database for your application by following theseinstructions.Note the database name.
Create a KMS key for your application by following theseinstructions. Copy theresource name of your created key.
Create a service account with the 'Cloud SQL Client' permissions byfollowing theseinstructions.
Add the 'Cloud KMS CryptoKey Encrypter/Decrypter' permission for the key toyour service account by following theseinstructions.
Create a connection pool and create a new table in the database.
Java
importcom.zaxxer.hikari.HikariConfig;importcom.zaxxer.hikari.HikariDataSource;importjava.security.GeneralSecurityException;importjava.sql.Connection;importjava.sql.PreparedStatement;importjava.sql.SQLException;importjavax.sql.DataSource;publicclassCloudSqlConnectionPool{publicstaticDataSourcecreateConnectionPool(StringdbUser,StringdbPass,StringdbName,StringinstanceConnectionName)throwsGeneralSecurityException{HikariConfigconfig=newHikariConfig();config.setJdbcUrl(String.format("jdbc:postgresql:///%s",dbName));config.setUsername(dbUser);// e.g. "root", "postgres"config.setPassword(dbPass);// e.g. "my-password"config.addDataSourceProperty("socketFactory","com.google.cloud.sql.postgres.SocketFactory");config.addDataSourceProperty("cloudSqlInstance",instanceConnectionName);DataSourcepool=newHikariDataSource(config);returnpool;}publicstaticvoidcreateTable(DataSourcepool,StringtableName)throwsSQLException{// Safely attempt to create the table schema.try(Connectionconn=pool.getConnection()){Stringstmt=String.format("CREATE TABLE IF NOT EXISTS %s ( "+"vote_id SERIAL NOT NULL, time_cast timestamp NOT NULL, team CHAR(6) NOT NULL,"+"voter_email BYTEA, PRIMARY KEY (vote_id) );",tableName);try(PreparedStatementcreateTableStatement=conn.prepareStatement(stmt);){createTableStatement.execute();}}}}
Python
importsqlalchemydefinit_tcp_connection_engine(db_user:str,db_pass:str,db_name:str,db_host:str)->sqlalchemy.engine.base.Engine:""" Creates a connection to the database using tcp socket. """# Remember - storing secrets in plaintext is potentially unsafe. Consider using# something like https://cloud.google.com/secret-manager/docs/overview to help keep# secrets secret.# Extract host and port from db_hosthost_args=db_host.split(":")db_hostname,db_port=host_args[0],int(host_args[1])pool=sqlalchemy.create_engine(# Equivalent URL:# postgresql+pg8000://<db_user>:<db_pass>@<db_host>:<db_port>/<db_name>sqlalchemy.engine.url.URL.create(drivername="postgresql+pg8000",username=db_user,# e.g. "my-database-user"password=db_pass,# e.g. "my-database-password"host=db_hostname,# e.g. "127.0.0.1"port=db_port,# e.g. 5432database=db_name,# e.g. "my-database-name"),)print("Created TCP connection pool")returnpooldefinit_unix_connection_engine(db_user:str,db_pass:str,db_name:str,instance_connection_name:str,db_socket_dir:str,)->sqlalchemy.engine.base.Engine:""" Creates a connection to the database using unix socket. """# Remember - storing secrets in plaintext is potentially unsafe. Consider using# something like https://cloud.google.com/secret-manager/docs/overview to help keep# secrets secret.pool=sqlalchemy.create_engine(# Equivalent URL:# mpostgresql+pg8000://<db_user>:<db_pass>@/<db_name>?unix_socket=<socket_path>/<cloud_sql_instance_name>sqlalchemy.engine.url.URL.create(drivername="postgresql+pg8000",username=db_user,# e.g. "my-database-user"password=db_pass,# e.g. "my-database-password"database=db_name,# e.g. "my-database-name"query={"unix_sock":"{}/{}/.s.PGSQL.5432".format(db_socket_dir,instance_connection_name# e.g. "/cloudsql")# i.e "<PROJECT-NAME>:<INSTANCE-REGION>:<INSTANCE-NAME>"},),)print("Created Unix socket connection pool")returnpooldefinit_db(db_user:str,db_pass:str,db_name:str,table_name:str,instance_connection_name:str=None,db_socket_dir:str=None,db_host:str=None,)->sqlalchemy.engine.base.Engine:"""Starts a connection to the database and creates voting table if it doesn't exist."""ifdb_host:db=init_tcp_connection_engine(db_user,db_pass,db_name,db_host)else:db=init_unix_connection_engine(db_user,db_pass,db_name,instance_connection_name,db_socket_dir)# Create tables (if they don't already exist)withdb.connect()asconn:conn.execute(f"CREATE TABLE IF NOT EXISTS{table_name} ""( vote_id SERIAL NOT NULL, time_cast timestamp NOT NULL, ""team VARCHAR(6) NOT NULL, voter_email BYTEA, ""PRIMARY KEY (vote_id) );")print(f"Created table{table_name} in db{db_name}")returndb
Initialize an envelope AEAD primitive with Tink.
Java
importcom.google.crypto.tink.Aead;importcom.google.crypto.tink.KmsClient;importcom.google.crypto.tink.aead.AeadConfig;importcom.google.crypto.tink.aead.AeadKeyTemplates;importcom.google.crypto.tink.aead.KmsEnvelopeAead;importcom.google.crypto.tink.integration.gcpkms.GcpKmsClient;importjava.security.GeneralSecurityException;publicclassCloudKmsEnvelopeAead{publicstaticAeadget(StringkmsUri)throwsGeneralSecurityException{AeadConfig.register();// Create a new KMS ClientKmsClientclient=newGcpKmsClient().withDefaultCredentials();// Create an AEAD primitive using the Cloud KMS keyAeadgcpAead=client.getAead(kmsUri);// Create an envelope AEAD primitive.// This key should only be used for client-side encryption to ensure authenticity and integrity// of data.returnnewKmsEnvelopeAead(AeadKeyTemplates.AES128_GCM,gcpAead);}}
Python
importloggingimporttinkfromtinkimportaeadfromtink.integrationimportgcpkmslogger=logging.getLogger(__name__)definit_tink_env_aead(key_uri:str,credentials:str)->tink.aead.KmsEnvelopeAead:""" Initiates the Envelope AEAD object using the KMS credentials. """aead.register()try:gcp_client=gcpkms.GcpKmsClient(key_uri,credentials)gcp_aead=gcp_client.get_aead(key_uri)excepttink.TinkErrorase:logger.error("Error initializing GCP client:%s",e)raisee# Create envelope AEAD primitive using AES256 GCM for encrypting the data# This key should only be used for client-side encryption to ensure authenticity and integrity# of data.key_template=aead.aead_key_templates.AES256_GCMenv_aead=aead.KmsEnvelopeAead(key_template,gcp_aead)print(f"Created envelope AEAD Primitive using KMS URI:{key_uri}")returnenv_aead
Encrypt data and insert it into the database.
Java
importcom.google.crypto.tink.Aead;importjava.security.GeneralSecurityException;importjava.sql.Connection;importjava.sql.PreparedStatement;importjava.sql.SQLException;importjava.sql.Timestamp;importjava.util.Date;importjavax.sql.DataSource;publicclassEncryptAndInsertData{publicstaticvoidmain(String[]args)throwsGeneralSecurityException,SQLException{// Saving credentials in environment variables is convenient, but not secure - consider a more// secure solution such as Cloud Secret Manager to help keep secrets safe.StringdbUser=System.getenv("DB_USER");// e.g. "root", "postgres"StringdbPass=System.getenv("DB_PASS");// e.g. "mysupersecretpassword"StringdbName=System.getenv("DB_NAME");// e.g. "votes_db"StringinstanceConnectionName=System.getenv("INSTANCE_CONNECTION_NAME");// e.g. "project-name:region:instance-name"StringkmsUri=System.getenv("CLOUD_KMS_URI");// e.g. "gcp-kms://projects/...path/to/key// Tink uses the "gcp-kms://" prefix for paths to keys stored in Google Cloud KMS. For more// info on creating a KMS key and getting its path, see// https://cloud.google.com/kms/docs/quickstartStringteam="TABS";StringtableName="votes";Stringemail="hello@example.com";// Initialize database connection pool and create table if it does not exist// See CloudSqlConnectionPool.java for setup detailsDataSourcepool=CloudSqlConnectionPool.createConnectionPool(dbUser,dbPass,dbName,instanceConnectionName);CloudSqlConnectionPool.createTable(pool,tableName);// Initialize envelope AEAD// See CloudKmsEnvelopeAead.java for setup detailsAeadenvAead=CloudKmsEnvelopeAead.get(kmsUri);encryptAndInsertData(pool,envAead,tableName,team,email);}publicstaticvoidencryptAndInsertData(DataSourcepool,AeadenvAead,StringtableName,Stringteam,Stringemail)throwsGeneralSecurityException,SQLException{try(Connectionconn=pool.getConnection()){Stringstmt=String.format("INSERT INTO %s (team, time_cast, voter_email) VALUES (?, ?, ?);",tableName);try(PreparedStatementvoteStmt=conn.prepareStatement(stmt);){voteStmt.setString(1,team);voteStmt.setTimestamp(2,newTimestamp(newDate().getTime()));// Use the envelope AEAD primitive to encrypt the email, using the team name as// associated data. This binds the encryption of the email to the team name, preventing// associating an encrypted email in one row with a team name in another row.byte[]encryptedEmail=envAead.encrypt(email.getBytes(),team.getBytes());voteStmt.setBytes(3,encryptedEmail);// Finally, execute the statement. If it fails, an error will be thrown.voteStmt.execute();System.out.println(String.format("Successfully inserted row into table %s",tableName));}}}}
Python
importdatetimeimportloggingimportosimportsqlalchemyimporttinkfrom.cloud_kms_env_aeadimportinit_tink_env_aeadfrom.cloud_sql_connection_poolimportinit_dblogger=logging.getLogger(__name__)defmain()->None:""" Connects to the database, encrypts and inserts some data. """db_user=os.environ["DB_USER"]# e.g. "root", "postgres"db_pass=os.environ["DB_PASS"]# e.g. "mysupersecretpassword"db_name=os.environ["DB_NAME"]# e.g. "votes_db"# Set if connecting using TCP:db_host=os.environ["DB_HOST"]# e.g. "127.0.0.1"# Set if connecting using Unix sockets:db_socket_dir=os.environ.get("DB_SOCKET_DIR","/cloudsql")instance_connection_name=os.environ["INSTANCE_CONNECTION_NAME"]# e.g. "project-name:region:instance-name"credentials=os.environ.get("GOOGLE_APPLICATION_CREDENTIALS","")key_uri="gcp-kms://"+os.environ["GCP_KMS_URI"]# e.g. "gcp-kms://projects/...path/to/key# Tink uses the "gcp-kms://" prefix for paths to keys stored in Google# Cloud KMS. For more info on creating a KMS key and getting its path, see# https://cloud.google.com/kms/docs/quickstarttable_name="votes"team="TABS"email="hello@example.com"env_aead=init_tink_env_aead(key_uri,credentials)db=init_db(db_user,db_pass,db_name,table_name,instance_connection_name,db_socket_dir,db_host,)encrypt_and_insert_data(db,env_aead,table_name,team,email)defencrypt_and_insert_data(db:sqlalchemy.engine.base.Engine,env_aead:tink.aead.KmsEnvelopeAead,table_name:str,team:str,email:str,)->None:""" Inserts a vote into the database with email address previously encrypted using a KmsEnvelopeAead object. """time_cast=datetime.datetime.now(tz=datetime.timezone.utc)# Use the envelope AEAD primitive to encrypt the email, using the team name as# associated data. Encryption with associated data ensures authenticity# (who the sender is) and integrity (the data has not been tampered with) of that# data, but not its secrecy. (see RFC 5116 for more info)encrypted_email=env_aead.encrypt(email.encode(),team.encode())# Verify that the team is one of the allowed optionsifteam!="TABS"andteam!="SPACES":logger.error(f"Invalid team specified:{team}")return# Preparing a statement before hand can help protect against injections.stmt=sqlalchemy.text(f"INSERT INTO{table_name} (time_cast, team, voter_email)"" VALUES (:time_cast, :team, :voter_email)")# Using a with statement ensures that the connection is always released# back into the pool at the end of statement (even if an error occurs)withdb.connect()asconn:conn.execute(stmt,time_cast=time_cast,team=team,voter_email=encrypted_email)print(f"Vote successfully cast for '{team}' at time{time_cast}!")
Query the database and decrypt the stored data.
Java
importcom.google.crypto.tink.Aead;importjava.security.GeneralSecurityException;importjava.sql.Connection;importjava.sql.PreparedStatement;importjava.sql.ResultSet;importjava.sql.SQLException;importjava.sql.Timestamp;importjavax.sql.DataSource;publicclassQueryAndDecryptData{publicstaticvoidmain(String[]args)throwsGeneralSecurityException,SQLException{// Saving credentials in environment variables is convenient, but not secure - consider a more// secure solution such as Cloud Secret Manager to help keep secrets safe.StringdbUser=System.getenv("DB_USER");// e.g. "root", "postgres"StringdbPass=System.getenv("DB_PASS");// e.g. "mysupersecretpassword"StringdbName=System.getenv("DB_NAME");// e.g. "votes_db"StringinstanceConnectionName=System.getenv("INSTANCE_CONNECTION_NAME");// e.g. "project-name:region:instance-name"StringkmsUri=System.getenv("CLOUD_KMS_URI");// e.g. "gcp-kms://projects/...path/to/key// Tink uses the "gcp-kms://" prefix for paths to keys stored in Google Cloud KMS. For more// info on creating a KMS key and getting its path, see// https://cloud.google.com/kms/docs/quickstartStringtableName="votes123";// Initialize database connection pool and create table if it does not exist// See CloudSqlConnectionPool.java for setup detailsDataSourcepool=CloudSqlConnectionPool.createConnectionPool(dbUser,dbPass,dbName,instanceConnectionName);CloudSqlConnectionPool.createTable(pool,tableName);// Initialize envelope AEAD// See CloudKmsEnvelopeAead.java for setup detailsAeadenvAead=CloudKmsEnvelopeAead.get(kmsUri);// Insert row into table to test// See EncryptAndInsert.java for setup detailsEncryptAndInsertData.encryptAndInsertData(pool,envAead,tableName,"SPACES","hello@example.com");queryAndDecryptData(pool,envAead,tableName);}publicstaticvoidqueryAndDecryptData(DataSourcepool,AeadenvAead,StringtableName)throwsGeneralSecurityException,SQLException{try(Connectionconn=pool.getConnection()){Stringstmt=String.format("SELECT team, time_cast, voter_email FROM %s ORDER BY time_cast DESC LIMIT 5",tableName);try(PreparedStatementvoteStmt=conn.prepareStatement(stmt);){ResultSetvoteResults=voteStmt.executeQuery();System.out.println("Team\tTime Cast\tEmail");while(voteResults.next()){Stringteam=voteResults.getString(1);TimestamptimeCast=voteResults.getTimestamp(2);// Postgres pads CHAR fields with spaces. These will need to be removed before// decrypting.Stringaad=voteResults.getString(1).trim();// Use the envelope AEAD primitive to encrypt the email, using the team name as// associated data. This binds the encryption of the email to the team name, preventing// associating an encrypted email in one row with a team name in another row.Stringemail=newString(envAead.decrypt(voteResults.getBytes(3),aad.getBytes()));System.out.println(String.format("%s\t%s\t%s",team,timeCast,email));}}}}}
Python
importosimportsqlalchemyimporttinkfrom.cloud_kms_env_aeadimportinit_tink_env_aeadfrom.cloud_sql_connection_poolimportinit_dbfrom.encrypt_and_insert_dataimportencrypt_and_insert_datadefmain()->None:""" Connects to the database, inserts encrypted data and retrieves encrypted data. """db_user=os.environ["DB_USER"]# e.g. "root", "postgres"db_pass=os.environ["DB_PASS"]# e.g. "mysupersecretpassword"db_name=os.environ["DB_NAME"]# e.g. "votes_db"# Set if connecting using TCP:db_host=os.environ["DB_HOST"]# e.g. "127.0.0.1"# Set if connecting using Unix sockets:db_socket_dir=os.environ.get("DB_SOCKET_DIR","/cloudsql")instance_connection_name=os.environ["INSTANCE_CONNECTION_NAME"]# e.g. "project-name:region:instance-name"credentials=os.environ.get("GOOGLE_APPLICATION_CREDENTIALS","")key_uri="gcp-kms://"+os.environ["GCP_KMS_URI"]# e.g. "gcp-kms://projects/...path/to/key# Tink uses the "gcp-kms://" prefix for paths to keys stored in Google# Cloud KMS. For more info on creating a KMS key and getting its path, see# https://cloud.google.com/kms/docs/quickstarttable_name="votes"team="TABS"email="hello@example.com"env_aead=init_tink_env_aead(key_uri,credentials)db=init_db(db_user,db_pass,db_name,table_name,instance_connection_name,db_socket_dir,db_host,)encrypt_and_insert_data(db,env_aead,table_name,team,email)query_and_decrypt_data(db,env_aead,table_name)defquery_and_decrypt_data(db:sqlalchemy.engine.base.Engine,env_aead:tink.aead.KmsEnvelopeAead,table_name:str,)->list[tuple[str]]:""" Retrieves data from the database and decrypts it using the KmsEnvelopeAead object. """withdb.connect()asconn:# Execute the query and fetch all resultsrecent_votes=conn.execute(f"SELECT team, time_cast, voter_email FROM{table_name} ""ORDER BY time_cast DESC LIMIT 5").fetchall()print("Team\tEmail\tTime Cast")output=[]forrowinrecent_votes:team=row[0]# Postgres pads CHAR fields with spaces. These will need to be removed before# decrypting.aad=team.rstrip()# Use the envelope AEAD primitive to decrypt the email, using the team name as# associated data. Encryption with associated data ensures authenticity# (who the sender is) and integrity (the data has not been tampered with) of that# data, but not its secrecy. (see RFC 5116 for more info)email=env_aead.decrypt(row[2],aad.encode()).decode()time_cast=row[1]# Print recent votesprint(f"{team}\t{email}\t{time_cast}")output.append((team,email,time_cast))returnoutput
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 2025-07-16 UTC.