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:
- ID
- Status (active, disabled)
- Type
- Key (the actual cryptographic key)
- Key material/parameters
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.jsonThe 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:
- Year 1: Your KeySet is encrypted under KEK-v1
- Year 2: The KMS automatically rotates to KEK-v2
- New encryptions use KEK-v2
- KEK-v1 is retained—old KeySets remain decryptable
- Year 3+: The cycle continues with KEK-v3, v4, etc.
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.