Manage Keys

Tink offers solutions to avoid improper key management, which is a major source of risk .

Create and rotate keys

After selecting a primitive and key type for your use case (in the preceding I want to... section), manage your keys with the external Key Management System (KMS) you chose :

  1. Create a key encryption key (KEK) in your KMS to protect your keys.

  2. Retrieve a key URI and key credentials from your KMS to pass to Tink.

  3. Use Tink's APIs or Tinkey to generate an encrypted keyset . After your keys are encrypted, you can store them wherever you want.

  4. Rotate your keys to avoid extensively reusing your keys and to recover from key compromise.

If you need to export keys, see Programmatically Export Key Material for details on doing so safely.

Step 1: Create a KEK in the external KMS

Create a key encryption key (KEK) in your external KMS. The KEK protects your keys by encrypting them, adding an extra layer of security.

Refer to KMS-specific documentation to create a KEK:

Step 2: Get a key URI and credentials

You can retrieve both a key URI and key credentials from your KMS.

Get the key URI

Tink requires a Uniform Resource Identifier (URI) to work with KMS keys.

To construct this URI, use the unique identifier the KMS assigns to the key when it is created . Add the appropriate KMS-specific prefix and follow the format of supported key URIs as described in this table:

KMS KMS identifier prefix Key URI format
AWS KMS
aws-kms:// aws-kms://arn:aws:kms:[region]:[account-id]:key/[key-id]
GCP KMS
gcp-kms:// gcp-kms://projects/*/locations/*/keyRings/*/cryptoKeys/*
HashiCorp Vault
hcvault:// hcvault://[key-id]

Get the key credentials

Prepare the necessary credentials so that Tink can authenticate to the external KMS.

The exact form of the credential is KMS-specific:

If you don't supply credentials, Tink attempts to load default credentials. For more information, refer to KMS-specific documentation:

Step 3: Create and store an encrypted keyset

Use Tink's APIs (for Google Cloud KMS, AWS KMS, or HashiCorp Vault) or Tinkey to generate a keyset, encrypt it using the external KMS, and store it somewhere.

Tinkey

 tinkey  
create-keyset  
--key-template  
AES128_GCM  
 \ 
  
--out-format  
json  
--out  
encrypted_aead_keyset.json  
 \ 
  
--master-key-uri  
gcp-kms://projects/tink-examples/locations/global/keyRings/foo/cryptoKeys/bar  
 \ 
  
--credential  
gcp_credentials.json 

Java

For this example you need the Google Cloud KMS extension tink-java-gcpkms .

 package 
  
 encryptedkeyset 
 ; 
 import static 
  
 java.nio.charset.StandardCharsets.UTF_8 
 ; 
 import 
  
 com.google.crypto.tink.Aead 
 ; 
 import 
  
 com.google.crypto.tink.KeysetHandle 
 ; 
 import 
  
 com.google.crypto.tink.TinkJsonProtoKeysetFormat 
 ; 
 import 
  
 com.google.crypto.tink.aead.AeadConfig 
 ; 
 import 
  
 com.google.crypto.tink.aead.PredefinedAeadParameters 
 ; 
 import 
  
 com.google.crypto.tink.integration.gcpkms.GcpKmsClient 
 ; 
 import 
  
 java.nio.file.Files 
 ; 
 import 
  
 java.nio.file.Path 
 ; 
 import 
  
 java.nio.file.Paths 
 ; 
 /** 
 * A command-line utility for working with encrypted keysets. 
 * 
 * <p>It requires the following arguments: 
 * 
 * <ul> 
 *   <li>mode: Can be "generate", "encrypt" or "decrypt". If mode is "generate", it will generate a 
 *       keyset, encrypt it and store it in the key-file argument. If mode is "encrypt" or 
 *       "decrypt", it will read and decrypt an keyset from the key-file argument, and use it to 
 *       encrypt or decrypt the input-file argument. 
 *   <li>kek-uri: Use this Cloud KMS' key as the key-encrypting-key for envelope encryption. 
 *   <li>gcp-credential-file: Use this JSON credential file to connect to Cloud KMS. 
 *   <li>input-file: If mode is "encrypt" or "decrypt", read the input from this file. 
 *   <li>output-file: If mode is "encrypt" or "decrypt", write the result to this file. 
 */ 
 public 
  
 final 
  
 class 
 EncryptedKeysetExample 
  
 { 
  
 private 
  
 static 
  
 final 
  
 String 
  
 MODE_ENCRYPT 
  
 = 
  
 "encrypt" 
 ; 
  
 private 
  
 static 
  
 final 
  
 String 
  
 MODE_DECRYPT 
  
 = 
  
 "decrypt" 
 ; 
  
 private 
  
 static 
  
 final 
  
 String 
  
 MODE_GENERATE 
  
 = 
  
 "generate" 
 ; 
  
 private 
  
 static 
  
 final 
  
 byte 
 [] 
  
 EMPTY_ASSOCIATED_DATA 
  
 = 
  
 new 
  
 byte 
 [ 
 0 
 ] 
 ; 
  
 public 
  
 static 
  
 void 
  
 main 
 ( 
 String 
 [] 
  
 args 
 ) 
  
 throws 
  
 Exception 
  
 { 
  
 if 
  
 ( 
 args 
 . 
 length 
  
 != 
  
 4 
 && 
 args 
 . 
 length 
  
 != 
  
 6 
 ) 
  
 { 
  
 System 
 . 
 err 
 . 
 printf 
 ( 
 "Expected 4 or 6 parameters, got %d\n" 
 , 
  
 args 
 . 
 length 
 ); 
  
 System 
 . 
 err 
 . 
 println 
 ( 
  
 "Usage: java EncryptedKeysetExample generate/encrypt/decrypt key-file kek-uri" 
  
 + 
  
 " gcp-credential-file input-file output-file" 
 ); 
  
 System 
 . 
 exit 
 ( 
 1 
 ); 
  
 } 
  
 String 
  
 mode 
  
 = 
  
 args 
 [ 
 0 
 ] 
 ; 
  
 if 
  
 ( 
 ! 
 mode 
 . 
 equals 
 ( 
 MODE_ENCRYPT 
 ) 
 && 
 ! 
 mode 
 . 
 equals 
 ( 
 MODE_DECRYPT 
 ) 
 && 
 ! 
 mode 
 . 
 equals 
 ( 
 MODE_GENERATE 
 )) 
  
 { 
  
 System 
 . 
 err 
 . 
 print 
 ( 
 "The first argument should be either encrypt, decrypt or generate" 
 ); 
  
 System 
 . 
 exit 
 ( 
 1 
 ); 
  
 } 
  
 Path 
  
 keyFile 
  
 = 
  
 Paths 
 . 
 get 
 ( 
 args 
 [ 
 1 
 ] 
 ); 
  
 String 
  
 kekUri 
  
 = 
  
 args 
 [ 
 2 
 ] 
 ; 
  
 String 
  
 gcpCredentialFilename 
  
 = 
  
 args 
 [ 
 3 
 ] 
 ; 
  
 // Initialise Tink: register all AEAD key types with the Tink runtime 
  
 AeadConfig 
 . 
 register 
 (); 
  
 // From the key-encryption key (KEK) URI, create a remote AEAD primitive for encrypting Tink 
  
 // keysets. 
  
 Aead 
  
 kekAead 
  
 = 
  
 new 
  
 GcpKmsClient 
 (). 
 withCredentials 
 ( 
 gcpCredentialFilename 
 ). 
 getAead 
 ( 
 kekUri 
 ); 
  
 if 
  
 ( 
 mode 
 . 
 equals 
 ( 
 MODE_GENERATE 
 )) 
  
 { 
  
 KeysetHandle 
  
 handle 
  
 = 
  
 KeysetHandle 
 . 
 generateNew 
 ( 
 PredefinedAeadParameters 
 . 
 AES128_GCM 
 ); 
  
 String 
  
 serializedEncryptedKeyset 
  
 = 
  
 TinkJsonProtoKeysetFormat 
 . 
 serializeEncryptedKeyset 
 ( 
  
 handle 
 , 
  
 kekAead 
 , 
  
 EMPTY_ASSOCIATED_DATA 
 ); 
  
 Files 
 . 
 write 
 ( 
 keyFile 
 , 
  
 serializedEncryptedKeyset 
 . 
 getBytes 
 ( 
 UTF_8 
 )); 
  
 return 
 ; 
  
 } 
  
 // Use the primitive to encrypt/decrypt files 
  
 // Read the encrypted keyset 
  
 KeysetHandle 
  
 handle 
  
 = 
  
 TinkJsonProtoKeysetFormat 
 . 
 parseEncryptedKeyset 
 ( 
  
 new 
  
 String 
 ( 
 Files 
 . 
 readAllBytes 
 ( 
 keyFile 
 ), 
  
 UTF_8 
 ), 
  
 kekAead 
 , 
  
 EMPTY_ASSOCIATED_DATA 
 ); 
  
 // Get the primitive 
  
 Aead 
  
 aead 
  
 = 
  
 handle 
 . 
 getPrimitive 
 ( 
 Aead 
 . 
 class 
 ); 
  
 Path 
  
 inputFile 
  
 = 
  
 Paths 
 . 
 get 
 ( 
 args 
 [ 
 4 
 ] 
 ); 
  
 Path 
  
 outputFile 
  
 = 
  
 Paths 
 . 
 get 
 ( 
 args 
 [ 
 5 
 ] 
 ); 
  
 if 
  
 ( 
 mode 
 . 
 equals 
 ( 
 MODE_ENCRYPT 
 )) 
  
 { 
  
 byte 
 [] 
  
 plaintext 
  
 = 
  
 Files 
 . 
 readAllBytes 
 ( 
 inputFile 
 ); 
  
 byte 
 [] 
  
 ciphertext 
  
 = 
  
 aead 
 . 
 encrypt 
 ( 
 plaintext 
 , 
  
 EMPTY_ASSOCIATED_DATA 
 ); 
  
 Files 
 . 
 write 
 ( 
 outputFile 
 , 
  
 ciphertext 
 ); 
  
 } 
  
 else 
  
 if 
  
 ( 
 mode 
 . 
 equals 
 ( 
 MODE_DECRYPT 
 )) 
  
 { 
  
 byte 
 [] 
  
 ciphertext 
  
 = 
  
 Files 
 . 
 readAllBytes 
 ( 
 inputFile 
 ); 
  
 byte 
 [] 
  
 plaintext 
  
 = 
  
 aead 
 . 
 decrypt 
 ( 
 ciphertext 
 , 
  
 EMPTY_ASSOCIATED_DATA 
 ); 
  
 Files 
 . 
 write 
 ( 
 outputFile 
 , 
  
 plaintext 
 ); 
  
 } 
  
 } 
  
 private 
  
 EncryptedKeysetExample 
 () 
  
 {} 
 } 
  

Go

 import 
  
 ( 
  
 "bytes" 
  
 "fmt" 
  
 "log" 
  
 "github.com/tink-crypto/tink-go/v2/aead" 
  
 "github.com/tink-crypto/tink-go/v2/keyset" 
  
 "github.com/tink-crypto/tink-go/v2/testing/fakekms" 
 ) 
 // The fake KMS should only be used in tests. It is not secure. 
 const 
  
 keyURI 
  
 = 
  
 "fake-kms://CM2b3_MDElQKSAowdHlwZS5nb29nbGVhcGlzLmNvbS9nb29nbGUuY3J5cHRvLnRpbmsuQWVzR2NtS2V5EhIaEIK75t5L-adlUwVhWvRuWUwYARABGM2b3_MDIAE" 
 func 
  
 Example_encryptedKeyset 
 () 
  
 { 
  
 // Get a KEK (key encryption key) AEAD. This is usually a remote AEAD to a KMS. In this example, 
  
 // we use a fake KMS to avoid making RPCs. 
  
 client 
 , 
  
 err 
  
 := 
  
 fakekms 
 . 
 NewClient 
 ( 
 keyURI 
 ) 
  
 if 
  
 err 
  
 != 
  
 nil 
  
 { 
  
 log 
 . 
 Fatal 
 ( 
 err 
 ) 
  
 } 
  
 kekAEAD 
 , 
  
 err 
  
 := 
  
 client 
 . 
 GetAEAD 
 ( 
 keyURI 
 ) 
  
 if 
  
 err 
  
 != 
  
 nil 
  
 { 
  
 log 
 . 
 Fatal 
 ( 
 err 
 ) 
  
 } 
  
 // Generate a new keyset handle for the primitive we want to use. 
  
 newHandle 
 , 
  
 err 
  
 := 
  
 keyset 
 . 
 NewHandle 
 ( 
 aead 
 . 
 AES256GCMKeyTemplate 
 ()) 
  
 if 
  
 err 
  
 != 
  
 nil 
  
 { 
  
 log 
 . 
 Fatal 
 ( 
 err 
 ) 
  
 } 
  
 // Choose some associated data. This is the context in which the keyset will be used. 
  
 keysetAssociatedData 
  
 := 
  
 [] 
 byte 
 ( 
 "keyset encryption example" 
 ) 
  
 // Encrypt the keyset with the KEK AEAD and the associated data. 
  
 buf 
  
 := 
  
 new 
 ( 
 bytes 
 . 
 Buffer 
 ) 
  
 writer 
  
 := 
  
 keyset 
 . 
 NewBinaryWriter 
 ( 
 buf 
 ) 
  
 err 
  
 = 
  
 newHandle 
 . 
 WriteWithAssociatedData 
 ( 
 writer 
 , 
  
 kekAEAD 
 , 
  
 keysetAssociatedData 
 ) 
  
 if 
  
 err 
  
 != 
  
 nil 
  
 { 
  
 log 
 . 
 Fatal 
 ( 
 err 
 ) 
  
 } 
  
 encryptedKeyset 
  
 := 
  
 buf 
 . 
 Bytes 
 () 
  
 // The encrypted keyset can now be stored. 
  
 // To use the primitive, we first need to decrypt the keyset. We use the same 
  
 // KEK AEAD and the same associated data that we used to encrypt it. 
  
 reader 
  
 := 
  
 keyset 
 . 
 NewBinaryReader 
 ( 
 bytes 
 . 
 NewReader 
 ( 
 encryptedKeyset 
 )) 
  
 handle 
 , 
  
 err 
  
 := 
  
 keyset 
 . 
 ReadWithAssociatedData 
 ( 
 reader 
 , 
  
 kekAEAD 
 , 
  
 keysetAssociatedData 
 ) 
  
 if 
  
 err 
  
 != 
  
 nil 
  
 { 
  
 log 
 . 
 Fatal 
 ( 
 err 
 ) 
  
 } 
  
 // Get the primitive. 
  
 primitive 
 , 
  
 err 
  
 := 
  
 aead 
 . 
 New 
 ( 
 handle 
 ) 
  
 if 
  
 err 
  
 != 
  
 nil 
  
 { 
  
 log 
 . 
 Fatal 
 ( 
 err 
 ) 
  
 } 
  
 // Use the primitive. 
  
 plaintext 
  
 := 
  
 [] 
 byte 
 ( 
 "message" 
 ) 
  
 associatedData 
  
 := 
  
 [] 
 byte 
 ( 
 "example encryption" 
 ) 
  
 ciphertext 
 , 
  
 err 
  
 := 
  
 primitive 
 . 
 Encrypt 
 ( 
 plaintext 
 , 
  
 associatedData 
 ) 
  
 if 
  
 err 
  
 != 
  
 nil 
  
 { 
  
 log 
 . 
 Fatal 
 ( 
 err 
 ) 
  
 } 
  
 decrypted 
 , 
  
 err 
  
 := 
  
 primitive 
 . 
 Decrypt 
 ( 
 ciphertext 
 , 
  
 associatedData 
 ) 
  
 if 
  
 err 
  
 != 
  
 nil 
  
 { 
  
 log 
 . 
 Fatal 
 ( 
 err 
 ) 
  
 } 
  
 fmt 
 . 
 Println 
 ( 
 string 
 ( 
 decrypted 
 )) 
  
 // Output: message 
 } 
  

Python

 """A command-line utility for generating, encrypting and storing keysets.""" 
 from 
  
 absl 
  
 import 
 app 
 from 
  
 absl 
  
 import 
 flags 
 from 
  
 absl 
  
 import 
 logging 
 import 
  
 tink 
 from 
  
 tink 
  
 import 
 aead 
 from 
  
 tink.integration 
  
 import 
 gcpkms 
 FLAGS 
 = 
 flags 
 . 
 FLAGS 
 flags 
 . 
 DEFINE_enum 
 ( 
 'mode' 
 , 
 None 
 , 
 [ 
 'generate' 
 , 
 'encrypt' 
 , 
 'decrypt' 
 ], 
 'The operation to perform.' 
 ) 
 flags 
 . 
 DEFINE_string 
 ( 
 'keyset_path' 
 , 
 None 
 , 
 'Path to the keyset used for encryption.' 
 ) 
 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 
 ( 
 'input_path' 
 , 
 None 
 , 
 'Path to the input file.' 
 ) 
 flags 
 . 
 DEFINE_string 
 ( 
 'output_path' 
 , 
 None 
 , 
 'Path to the output file.' 
 ) 
 flags 
 . 
 DEFINE_string 
 ( 
 'associated_data' 
 , 
 None 
 , 
 'Optional associated data to use with the ' 
 'encryption operation.' 
 ) 
 def 
  
 main 
 ( 
 argv 
 ): 
 del 
 argv 
 # Unused. 
 associated_data 
 = 
 b 
 '' 
 if 
 not 
 FLAGS 
 . 
 associated_data 
 else 
 bytes 
 ( 
 FLAGS 
 . 
 associated_data 
 , 
 'utf-8' 
 ) 
 # Initialise Tink 
 aead 
 . 
 register 
 () 
 try 
 : 
 # Read the GCP credentials and setup client 
 client 
 = 
 gcpkms 
 . 
 GcpKmsClient 
 ( 
 FLAGS 
 . 
 kek_uri 
 , 
 FLAGS 
 . 
 gcp_credential_path 
 ) 
 except 
 tink 
 . 
 TinkError 
 as 
 e 
 : 
 logging 
 . 
 exception 
 ( 
 'Error creating GCP KMS client: 
 %s 
 ' 
 , 
 e 
 ) 
 return 
 1 
 # Create envelope AEAD primitive using AES256 GCM for encrypting the data 
 try 
 : 
 remote_aead 
 = 
 client 
 . 
 get_aead 
 ( 
 FLAGS 
 . 
 kek_uri 
 ) 
 except 
 tink 
 . 
 TinkError 
 as 
 e 
 : 
 logging 
 . 
 exception 
 ( 
 'Error creating primitive: 
 %s 
 ' 
 , 
 e 
 ) 
 return 
 1 
 if 
 FLAGS 
 . 
 mode 
 == 
 'generate' 
 : 
 # Generate a new keyset 
 try 
 : 
 key_template 
 = 
 aead 
 . 
 aead_key_templates 
 . 
 AES128_GCM 
 keyset_handle 
 = 
 tink 
 . 
 new_keyset_handle 
 ( 
 key_template 
 ) 
 except 
 tink 
 . 
 TinkError 
 as 
 e 
 : 
 logging 
 . 
 exception 
 ( 
 'Error creating primitive: 
 %s 
 ' 
 , 
 e 
 ) 
 return 
 1 
 # Encrypt the keyset_handle with the remote key-encryption key (KEK) 
 with 
 open 
 ( 
 FLAGS 
 . 
 keyset_path 
 , 
 'wt' 
 ) 
 as 
 keyset_file 
 : 
 try 
 : 
 keyset_encryption_associated_data 
 = 
 'encrypted keyset example' 
 serialized_encrypted_keyset 
 = 
 ( 
 tink 
 . 
 json_proto_keyset_format 
 . 
 serialize_encrypted 
 ( 
 keyset_handle 
 , 
 remote_aead 
 , 
 keyset_encryption_associated_data 
 ) 
 ) 
 keyset_file 
 . 
 write 
 ( 
 serialized_encrypted_keyset 
 ) 
 except 
 tink 
 . 
 TinkError 
 as 
 e 
 : 
 logging 
 . 
 exception 
 ( 
 'Error writing key: 
 %s 
 ' 
 , 
 e 
 ) 
 return 
 1 
 return 
 0 
 # Use the keyset to encrypt/decrypt data 
 # Read the encrypted keyset into a keyset_handle 
 with 
 open 
 ( 
 FLAGS 
 . 
 keyset_path 
 , 
 'rt' 
 ) 
 as 
 keyset_file 
 : 
 try 
 : 
 serialized_encrypted_keyset 
 = 
 keyset_file 
 . 
 read 
 () 
 keyset_encryption_associated_data 
 = 
 'encrypted keyset example' 
 keyset_handle 
 = 
 tink 
 . 
 json_proto_keyset_format 
 . 
 parse_encrypted 
 ( 
 serialized_encrypted_keyset 
 , 
 remote_aead 
 , 
 keyset_encryption_associated_data 
 , 
 ) 
 except 
 tink 
 . 
 TinkError 
 as 
 e 
 : 
 logging 
 . 
 exception 
 ( 
 'Error reading key: 
 %s 
 ' 
 , 
 e 
 ) 
 return 
 1 
 # Get the primitive 
 try 
 : 
 cipher 
 = 
 keyset_handle 
 . 
 primitive 
 ( 
 aead 
 . 
 Aead 
 ) 
 except 
 tink 
 . 
 TinkError 
 as 
 e 
 : 
 logging 
 . 
 exception 
 ( 
 'Error creating primitive: 
 %s 
 ' 
 , 
 e 
 ) 
 return 
 1 
 with 
 open 
 ( 
 FLAGS 
 . 
 input_path 
 , 
 'rb' 
 ) 
 as 
 input_file 
 : 
 input_data 
 = 
 input_file 
 . 
 read 
 () 
 if 
 FLAGS 
 . 
 mode 
 == 
 'decrypt' 
 : 
 output_data 
 = 
 cipher 
 . 
 decrypt 
 ( 
 input_data 
 , 
 associated_data 
 ) 
 elif 
 FLAGS 
 . 
 mode 
 == 
 'encrypt' 
 : 
 output_data 
 = 
 cipher 
 . 
 encrypt 
 ( 
 input_data 
 , 
 associated_data 
 ) 
 else 
 : 
 logging 
 . 
 error 
 ( 
 'Unsupported mode 
 %s 
 . Please choose "encrypt" or "decrypt".' 
 , 
 FLAGS 
 . 
 mode 
 , 
 ) 
 return 
 1 
 with 
 open 
 ( 
 FLAGS 
 . 
 output_path 
 , 
 'wb' 
 ) 
 as 
 output_file 
 : 
 output_file 
 . 
 write 
 ( 
 output_data 
 ) 
 if 
 __name__ 
 == 
 '__main__' 
 : 
 flags 
 . 
 mark_flags_as_required 
 ([ 
 'mode' 
 , 
 'keyset_path' 
 , 
 'kek_uri' 
 , 
 'gcp_credential_path' 
 ]) 
 app 
 . 
 run 
 ( 
 main 
 ) 
  

Step 4: Rotate keys

To ensure the security of your system, you must rotate keys.

  1. Enable automatic key rotation in your KMS.
  2. Determine a suitable frequency for rotating keys. This depends on how sensitive your data is, how many messages you need to encrypt, and whether you have to coordinate the rotation with external partners.

    • For symmetric encryption, use 30- to 90-day keys.
    • For asymmetric encryption, the rotation frequency can be lower, but only if you can securely revoke keys.

Learn more about key rotation in KMS-specific documentation:

Design a Mobile Site
View Site in Mobile | Classic
Share by: