Martin's Blog

Making it easy to implement cryptography in applications: Google Tink

Most (legacy) cryptography libraries are designed as toolboxes, not guardrails. They offer maximum flexibility—dozens of algorithms, configurable parameters, multiple modes of operation—but assume the developer already knows which choices are safe. I.e. they require specialist knowledge that most people do not have.

In practice, this leads to predictable failures: developers pick outdated algorithms, misunderstand mode requirements, or make subtle but catastrophic mistakes like reusing nonces or deriving keys insecurely.

Modern libraries take the opposite approach. They expose algorithm-agnostic, high-level APIs (encrypt, decrypt, sign, verify) with secure algorithms and parameters chosen under the hood. Dangerous options are removed, not documented.

There’s another advantage: cryptographic agility. Because application code doesn’t reference specific algorithms, the library can migrate from AES-GCM to a post-quantum scheme—or deprecate a weakened primitive—without touching a single line of your code.

The goal for modern libraries shifts from enabling experts to protecting everyone.

Aspect Traditional Libraries Modern Libraries
Algorithm selection Manual (AES, 3DES, ChaCha20, RSA, …) Opinionated defaults
Configuration Key size, mode, padding, IV handling Minimal or none
Misuse resistance None — easy to reuse nonces, IVs, keys Designed to prevent common errors

Even so, critical challenges remain: How are keys stored and rotated? How do you track which algorithm version encrypted which data? Modern libraries handle the cryptography—but key management and operational metadata are still your problem.

Google Tink

“Cryptography takes security and other problems and turns them into a key management problem.” Peter G. Neumann

Enter Google Tink. Tink is a family of cryptographic libraries developed and maintained by Google, with support for multiple programming languages. Its lineage traces back to Keyczar, another Google project from the mid-2000s (I actually contributed some elliptic curve cryptography code to Keyczar back in 2008).

The team behind Keyczar recognized early on that safe algorithm selection and secure defaults weren’t enough—they needed to solve key management too.

Keyczar’s goal is to make it easier to safely use cryptography. Developers should not be able to inadvertently expose key material, use weak key lengths or deprecated algorithms, or improperly use cryptographic modes. Keyczar supports sets of multiple key versions that allow the programmer to easily rotate and retire keys.

Those design principles have carried forward into Tink.

To address the key management problem, Tink introduces the KeySet: a structured collection of keys where each entry contains everything required for a given cryptographic operation: algorithm identifier, parameters, key material, and status. This self-contained design is what enables Tink’s transparent key rotation and selection:

The primary key in a KeySet performs all active operations—encrypting, signing. Enabled non-primary keys remain available for passive operations like decryption, allowing seamless key rotation. Since every ciphertext is prefixed with the originating key’s ID, the library handles key selection transparently.

Protecting the KeySet itself is critical. Tink encourages using a Key Management Service (KMS) to encrypt KeySets at rest. At runtime, Tink decrypts the KeySet transparently and operates on the plaintext keys in memory. Since this encryption is handled externally by the KMS, rotating the KMS key is also straightforward. Supported KMS providers include Google Cloud KMS, AWS KMS, and HashiCorp Vault (currently Go only).

Of course, this shifts the problem one level up: the KMS credentials themselves must now be protected. Key management is never truly “solved”—just carefully layered:

“Cryptography takes security and other problems and turns them into a key management problem.” Peter G. Neumann

Using Tink

To use Tink, you start by selecting a primitive that matches your use case. Each primitive has a corresponding interface (e.g., AEAD). All implementations of that interface achieve the same security goal. Different choices may have different performance characteristics, but all are secure.

Primitive Description Use Case
AEAD Authenticated Encryption with Associated Data Encrypt data with integrity protection
Streaming AEAD AEAD for large data streams Encrypt large files without loading into memory
Deterministic AEAD Same plaintext → same ciphertext Searchable encryption, deduplication
MAC Message Authentication Code Verify data integrity (symmetric)
PRF Pseudo-Random Function Key derivation, deterministic hashing
Digital Signature Asymmetric sign/verify Non-repudiation, authenticity
Hybrid Encryption Asymmetric encryption Encrypt for a recipient’s public key
JWT MAC JSON Web Token with HMAC Symmetric token signing
JWT Signature JSON Web Token with signatures Asymmetric token signing

The Tink team provides the following opinionated recommendations:

Use Case Recommended Algorithm
General encryption AES-256-GCM
Large file encryption AES-GCM-HKDF-Streaming
Message authentication HMAC-SHA256
Digital signatures Ed25519 or ECDSA P-256
Hybrid encryption HPKE X25519 + AES-256-GCM
JWT signing ES256 (ECDSA) or RS256 (RSA)
Nonce-misuse resistant AES-GCM-SIV or XChaCha20-Poly1305

These recommendations inform KeySet creation, which can be done programmatically or using Tink’s tinkey command-line tool.

Encrypting data

To keep things simple, let’s start by creating a new KeySet using the recommended settings, but without KMS integration:

$ tinkey create-keyset --key-template AES256_GCM --out "$KEYS_DIR/aead_keyset.json"

The resulting KeySet looks like this:

{
  "primaryKeyId": 2022085761,
  "key": [
    {
      "keyData": {
        "typeUrl": "type.googleapis.com/google.crypto.tink.AesGcmKey",
        "value": "GiDPaLUDkDdTiWT1NFUEGvbJEVZ6SJDkM/CQJpJE2FOMpQ==",
        "keyMaterialType": "SYMMETRIC"
      },
      "status": "ENABLED",
      "keyId": 2022085761,
      "outputPrefixType": "TINK"
    }
  ]
}

We can now use the KeySet to encrypt data using the primary key. Note that the code contains no references to algorithms or parameters. Only the primitive is referenced. Everything else is driven by the key itself:

 1// Load keyset
 2aeadKeyset, err := loadKeyset("keys/aead_keyset.json")
 3if err != nil {
 4	return fmt.Errorf("failed to load AEAD keyset: %w", err)
 5}
 6
 7// Get the AEAD primitive from the keyset
 8aeadPrimitive, err := aead.New(aeadKeyset)
 9if err != nil {
10	return fmt.Errorf("failed to create AEAD primitive: %w", err)
11}
12
13// Original plaintext and associated data
14plaintext := []byte("This is a secret message that needs encryption!")
15associatedData := []byte("metadata-context-info")
16
17// Encrypt the plaintext
18ciphertext, err := aeadPrimitive.Encrypt(plaintext, associatedData)
19if err != nil {
20	return fmt.Errorf("encryption failed: %w", err)
21}
22fmt.Printf("Ciphertext (base64): %s\n", base64.StdEncoding.EncodeToString(ciphertext))
23
24// Decrypt the ciphertext
25decrypted, err := aeadPrimitive.Decrypt(ciphertext, associatedData)
26if err != nil {
27	return fmt.Errorf("decryption failed: %w", err)
28}
29fmt.Printf("Decrypted plaintext: %s\n", decrypted)
30
31// Verify decryption worked correctly
32if string(decrypted) == string(plaintext) {
33	fmt.Println("✓ AEAD encryption/decryption successful!")
34}
35
36// Demonstrate that wrong associated data causes decryption failure
37_, err = aeadPrimitive.Decrypt(ciphertext, []byte("wrong-associated-data"))
38if err != nil {
39	fmt.Println("✓ Decryption correctly failed with wrong associated data")
40}

By default, Tink prepends the key ID and IV to the ciphertext, enabling automatic key selection during decryption. RAW output variants are available if you need ciphertext without the key ID prefix.

Over time, you can add new keys and perform key rotation:

$ tinkey add-key --key-template AES256_GCM --in keys/aead_keyset.json --out keys/aead_keyset2.json
$ tinkey add-key --key-template AES256_GCM --in keys/aead_keyset2.json --out keys/aead_keyset3.json
$ tinkey promote-key --in keys/aead_keyset3.json --key-id 3048940963 --out keys/aead_keyset4.json
$ tinkey disable-key --in keys/aead_keyset4.json --key-id 2127300875 --out keys/aead_keyset5.json

The resulting KeySet now looks like this:

{
  "primaryKeyId": 3048940963,
  "key": [
    {
      "keyData": {
        "typeUrl": "type.googleapis.com/google.crypto.tink.AesGcmKey",
        "value": "GiDPaLUDkDdTiWT1NFUEGvbJEVZ6SJDkM/CQJpJE2FOMpQ==",
        "keyMaterialType": "SYMMETRIC"
      },
      "status": "ENABLED",
      "keyId": 2022085761,
      "outputPrefixType": "TINK"
    },
    {
      "keyData": {
        "typeUrl": "type.googleapis.com/google.crypto.tink.AesGcmKey",
        "value": "GiDHLsuj7jJwRfL9A6u9Fu2zhv+HGG4M3r5jviYsXmQhZg==",
        "keyMaterialType": "SYMMETRIC"
      },
      "status": "DISABLED",
      "keyId": 2127300875,
      "outputPrefixType": "TINK"
    },
    {
      "keyData": {
        "typeUrl": "type.googleapis.com/google.crypto.tink.AesGcmKey",
        "value": "GiAH3zNXYp8EpArHXXeuTPTGHtppfA/To2OC8r3UJ4mJnw==",
        "keyMaterialType": "SYMMETRIC"
      },
      "status": "ENABLED",
      "keyId": 3048940963,
      "outputPrefixType": "TINK"
    }
  ]
}

Using a KMS

The examples above didn’t use a KMS to protect the KeySet. Let’s briefly look at the benefits of adding one:

Benefit Description
Secure key storage Master keys stored in HSM-backed infrastructure
Access control IAM policies control who can use keys
Audit logging Every key access is logged (CloudTrail, etc.)
Key rotation KMS handles master key rotation transparently
Separation of duties Security team manages KMS, devs use wrapped keysets
Compliance Meets requirements for FIPS 140-2, SOC2, PCI-DSS, etc.
No secrets in code Encrypted keysets can be committed to version control

Note: KMS integration doesn’t eliminate the key protection problem—it shifts it. You’re still responsible for securing the credentials that authenticate to the KMS.

Master Key Rotation

The KeySet is encrypted using a Key Encryption Key (KEK) that never leaves the KMS—it’s stored and used exclusively within the KMS boundary. Key rotation for the KEK is handled automatically by the KMS, transparent to your application.

This is illustrated in the diagram below:

┌─────────────────────────────────────────────────────────────┐
│ Level 1: KMS Master Key (KEK)                               │
│ - Managed by KMS                                            │
│ - Encrypts/decrypts your Tink keyset                        │
└─────────────────────────────────────────────────────────────┘
                              │
                              │ protects
                              ▼
┌─────────────────────────────────────────────────────────────┐
│ Level 2: Tink Keyset (DEKs)                                 │
│ - Contains one or more data encryption keys                 │
│ - One key is marked "primary" (used for new encryptions)    │
│ - Old keys kept for decrypting historical data              │
└─────────────────────────────────────────────────────────────┘
                              │
                              │ encrypts
                              ▼
┌─────────────────────────────────────────────────────────────┐
│ Your Data                                                   │
└─────────────────────────────────────────────────────────────┘

This is how KEK rotation typically unfolds:

The critical point: your application code never changes. The KMS handles version tracking internally.

Summary

Tink is designed for application-level cryptography—encrypting data, signing messages, managing keys. It’s not intended for low-level use cases or implementing network protocols like TLS or SSH, where libraries like libsodium (NaCl) or OpenSSL are more appropriate.

Tink makes the right thing easy and the wrong thing hard. Instead of exposing dozens of algorithms and hoping developers choose wisely, it provides simple primitives with secure defaults baked in. Key management—often an afterthought in other libraries—is central to Tink’s design, with built-in support for key rotation and KMS integration.

If you’re building applications that handle sensitive data, Tink offers a rare combination: strong security without requiring deep cryptographic expertise.

Supported languages: Java, Go, C++, Python, and Objective-C.

Learn more at https://developers.google.com/tink.