Tink offers solutions to avoid improper key management, which is a [major source
of risk](https://cloudblog-withgoogle-com.cdn.ampproject.org/c/s/cloudblog.withgoogle.com/products/identity-security/better-encrypt-your-security-keys-in-google-cloud/amp/).

## 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](https://developers.google.com/tink/select-kms):

1. [Create a key encryption key (KEK)](https://developers.google.com/tink/key-management-overview#create_kek) in your KMS to protect your
   keys.

2. Retrieve a [key URI and key credentials](https://developers.google.com/tink/key-management-overview#get_uri_credentials) from your KMS
   to pass to Tink.

3. Use Tink's APIs or Tinkey to [generate an encrypted
   keyset](https://developers.google.com/tink/key-management-overview#encrypt_keyset). After your keys are encrypted, you can store them
   wherever you want.

4. [Rotate your keys](https://developers.google.com/tink/key-management-overview#rotate_keys) to avoid extensively reusing your keys and
   to recover from key compromise.

If you need to export keys, see [Programmatically Export Key
Material](https://developers.google.com/tink/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:

- [Google Cloud KMS](https://cloud.google.com/kms/docs/quickstart)
- [Amazon KMS](http://docs.aws.amazon.com/kms/latest/developerguide/create-keys.html)
- [HashiCorp Vault](https://www.vaultproject.io/docs/secrets/transit) (currently only available in Go language)

> [!NOTE]
> **Note:** If you are using Tink's Java implementation with an external KMS, make sure to add the [appropriate dependency](https://developers.google.com/tink/tink-setup#java) to your Java config.

### 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](https://developers.google.com/tink/key-management-overview#create_kek). 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]` |

> [!NOTE]
> **Note:** HashiCorp Vault is only supported by [tink-go](https://github.com/tink-crypto/tink-go-hcvault). Other KMSs are supported in all Tink implementations and by [Tinkey](https://developers.google.com/tink/tinkey-overview).

**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:

- Google Cloud KMS -- Tink requires [service accounts credentials](https://cloud.google.com/iam/docs/service-account-overview#credentials). These are a JSON file that can be [created and downloaded from the Google Cloud
  Console](https://cloud.google.com/iam/docs/create-short-lived-credentials-direct#service-account).
- AWS KMS -- Tink requires a [credentials file](https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-files.html) that contains
  - the [access key ID](https://docs.aws.amazon.com/powershell/latest/userguide/pstools-appendix-sign-up.html) in the `accessKey` property and
  - the [secret access key](https://docs.aws.amazon.com/powershell/latest/userguide/pstools-appendix-sign-up.html) in the `secretKey` property.
- HashiCorp Vault -- Tink requires service tokens that can be created by the [vault token create
  command](https://www.vaultproject.io/docs/commands/token/create).

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

- [Google Cloud KMS](https://developers.google.com/identity/protocols/application-default-credentials)
- [AWS KMS](http://docs.aws.amazon.com/sdk-for-java/v1/developer-guide/credentials.html#credentials-default)

### Step 3: Create and store an encrypted keyset

Use Tink's APIs (for Google Cloud KMS, AWS KMS, or HashiCorp Vault) or
[Tinkey](https://developers.google.com/tink/tinkey-overview) to generate a keyset, encrypt it using the external
KMS, and store it somewhere.

> [!WARNING]
> **Warning:** While you can use Tink to [generate unencrypted, plaintext
> keysets](https://developers.google.com/tink/generate-plaintext-keyset), we don't recommend doing so since keys are often leaked when stored in plaintext.

### 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`](https://github.com/tink-crypto/tink-java-gcpkms).

```java
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.RegistryConfiguration;
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(RegistryConfiguration.get(), 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

```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

```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 AES128 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:

- [Google Cloud KMS](https://cloud.google.com/kms/docs/key-rotation)
- [Amazon KMS](https://docs.aws.amazon.com/kms/latest/developerguide/rotate-keys.html)
- [HashiCorp Vault](https://www.vaultproject.io/docs/internals/rotation)

> [!IMPORTANT]
> **Key Point:** Key rotation is an essential best practice that prevents keys from being extensively reused. Regular key rotation limits the number of messages encrypted with the same key version, minimizes the number of potentially vulnerable messages, and ensures your system is resilient.