Secure Anything with Google Tink: Encrypt Everywhere in Just Three Lines
This article introduces Google Tink, an open‑source cryptographic library for Java, explains its design goals and primitive abstractions, and walks through concise code examples that generate keysets, perform AEAD encryption, stream large files, compute MACs, and create digital signatures, all with minimal boilerplate.
Introduction
Google Tink is an open‑source cryptographic library created by Google security engineers. It offers a user‑centric API, rigorous implementation, code review, and comprehensive testing to reduce common encryption bugs.
Tink Goals and Mechanisms
Cryptographic agility : Users can switch keys and algorithms easily.
Secure reviewability : Clear security‑guaranteed interfaces enable local code review.
Tink abstracts cryptographic primitives and introduces the concept of a keyset , a collection of keys bound to a specific primitive, allowing code to work with multiple keys without specifying algorithms directly.
Core Primitives
Typical primitives include Aead, PublicKeySign, and Mac. These are accessed via the KeysetHandle class.
1. Setting Up the Environment
Java 21 is used. Add the following Maven dependencies:
<dependency>
<groupId>com.google.crypto.tink</groupId>
<artifactId>tink</artifactId>
<version>1.20.0</version>
</dependency>
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>4.34.0</version>
</dependency>2. Generating a New Keyset
TinkConfig.register();
// Generate a new keyset for AES‑128‑GCM
KeysetHandle handle = KeysetHandle.generateNew(PredefinedAeadParameters.AES128_GCM);
// Serialize the keyset for storage
String serializedKeyset = TinkJsonProtoKeysetFormat.serializeKeyset(handle, InsecureSecretKeyAccess.get());
System.err.println(serializedKeyset);
// Parse the keyset back
handle = TinkJsonProtoKeysetFormat.parseKeyset(serializedKeyset, InsecureSecretKeyAccess.get());The resulting JSON contains fields such as primaryKeyId, typeUrl, and keyMaterialType, ensuring each key uniquely identifies a cryptographic function.
3. Symmetric AEAD Encryption & Decryption
AeadConfig.register();
KeysetHandle handle = KeysetHandle.generateNew(PredefinedAeadParameters.AES128_GCM);
String serializedKeyset = TinkJsonProtoKeysetFormat.serializeKeyset(handle, InsecureSecretKeyAccess.get());
String plaintext = "Spring全家桶实战案例";
String associatedData = "Pack_xg";
Aead aead = handle.getPrimitive(RegistryConfiguration.get(), Aead.class);
byte[] ciphertext = aead.encrypt(plaintext.getBytes(), associatedData.getBytes());
String encBase64 = Base64.getEncoder().encodeToString(ciphertext);
System.err.println(encBase64);
// Decrypt
handle = TinkJsonProtoKeysetFormat.parseKeyset(serializedKeyset, InsecureSecretKeyAccess.get());
aead = handle.getPrimitive(RegistryConfiguration.get(), Aead.class);
byte[] decrypted = aead.decrypt(Base64.getDecoder().decode(encBase64), associatedData.getBytes(StandardCharsets.UTF_8));
System.err.println(new String(decrypted, StandardCharsets.UTF_8));The second parameter, associatedData, acts as an authentication tag that must be identical during decryption; it is not encrypted but bound to the ciphertext.
4. Encrypting Large Files or Streams
For large files, use the streaming AEAD primitive AES128_GCM_HKDF_1MB. Example:
String keyset = "<em>generated keyset JSON</em>";
Path inputFile = Paths.get("E:\\test\\key\\data.txt");
Path outputFile = Paths.get("E:\\test\\key\\data_encrypt.txt");
byte[] associatedData = "xxxooo".getBytes();
StreamingAeadConfig.register();
KeysetHandle handle = TinkJsonProtoKeysetFormat.parseKeyset(keyset, InsecureSecretKeyAccess.get());
StreamingAead streamingAead = handle.getPrimitive(RegistryConfiguration.get(), StreamingAead.class);
// Encrypt
encryptFile(streamingAead, inputFile, outputFile, associatedData);
// Decrypt
Path encrypted = Paths.get("E:\\test\\key\\data_encrypt.txt");
Path decrypted = Paths.get("E:\\test\\key\\data_plain.txt");
decryptFile(streamingAead, encrypted, decrypted, associatedData);
public static void encryptFile(StreamingAead streamingAead, Path inputFile, Path outputFile, byte[] associatedData) throws GeneralSecurityException, IOException {
try (WritableByteChannel encryptingChannel = streamingAead.newEncryptingChannel(
FileChannel.open(outputFile, StandardOpenOption.WRITE, StandardOpenOption.CREATE), associatedData);
FileChannel inputChannel = FileChannel.open(inputFile, StandardOpenOption.READ)) {
ByteBuffer buffer = ByteBuffer.allocate(8 * 1024);
while (true) {
int read = inputChannel.read(buffer);
if (read <= 0) return;
buffer.flip();
while (buffer.hasRemaining()) encryptingChannel.write(buffer);
buffer.clear();
}
}
}
public static void decryptFile(StreamingAead streamingAead, Path inputFile, Path outputFile, byte[] associatedData) throws GeneralSecurityException, IOException {
try (ReadableByteChannel decryptingChannel = streamingAead.newDecryptingChannel(
FileChannel.open(inputFile, StandardOpenOption.READ), associatedData);
FileChannel outputChannel = FileChannel.open(outputFile, StandardOpenOption.WRITE, StandardOpenOption.CREATE)) {
ByteBuffer buffer = ByteBuffer.allocate(8 * 1024);
while (true) {
int read = decryptingChannel.read(buffer);
if (read <= 0) return;
buffer.flip();
while (buffer.hasRemaining()) outputChannel.write(buffer);
buffer.clear();
}
}
}The accompanying image shows the before‑and‑after size of an encrypted file.
5. Message Authentication (MAC)
For integrity‑only protection, use the HMAC_SHA256 MAC primitive. Example:
String keyset = "<em>generated keyset JSON</em>";
byte[] msg = "Spring Boot3实战案例300讲".getBytes(StandardCharsets.UTF_8);
MacConfig.register();
KeysetHandle handle = TinkJsonProtoKeysetFormat.parseKeyset(keyset, InsecureSecretKeyAccess.get());
Mac mac = handle.getPrimitive(RegistryConfiguration.get(), Mac.class);
String macData = Base64.getEncoder().encodeToString(mac.computeMac(msg));
System.err.println(macData);
// Verify (throws if invalid)
mac.verifyMac(Base64.getDecoder().decode(macData), msg);In most scenarios, using AEAD (which combines encryption and MAC) is preferable to MAC alone.
6. Digital Signatures
For non‑repudiation, the library provides ECDSA P256 signatures. Example:
String privateKeyset = "<em>private key JSON</em>";
byte[] msg = "Spring Boot3实战案例300讲".getBytes(StandardCharsets.UTF_8);
SignatureConfig.register();
KeysetHandle handle = TinkJsonProtoKeysetFormat.parseKeyset(privateKeyset, InsecureSecretKeyAccess.get());
PublicKeySign signer = handle.getPrimitive(RegistryConfiguration.get(), PublicKeySign.class);
byte[] signature = signer.sign(msg);
System.err.println("Signature: %s".formatted(Base64.getEncoder().encodeToString(signature)));
String publicKeyset = "<em>public key JSON</em>";
handle = TinkJsonProtoKeysetFormat.parseKeyset(publicKeyset, InsecureSecretKeyAccess.get());
PublicKeyVerify verifier = handle.getPrimitive(RegistryConfiguration.get(), PublicKeyVerify.class);
verifier.verify(signature, msg); // throws if verification failsNote that the private key is used for signing while the public key is required for verification.
Conclusion
Google Tink provides a clean, opinionated API that abstracts away low‑level cryptographic details while offering strong guarantees through well‑tested primitives, keyset management, and support for both symmetric and asymmetric operations. With just a few lines of code developers can achieve end‑to‑end encryption, integrity, and authenticity across a wide range of use cases.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Spring Full-Stack Practical Cases
Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
