Security Top

I just announced the new Learn Spring Security course, including the full material focused on the new OAuth2 stack in Spring Security 5:


1. Introduction

Nowadays, many developers use cryptographic techniques to protect user data.

In cryptography, small implementation errors can have serious consequences, and understanding how to implement cryptography correctly is a complex and time-consuming task.

In this tutorial, we're going to describe Tink – a multi-language, cross-platform cryptographic library that can help us to implement secure, cryptographic code.

2. Dependencies

We can use Maven or Gradle to import Tink.

For our tutorial, we'll just add Tink's Maven dependency:


Though we could have used Gradle instead:

dependencies {
  compile ''

3. Initialization

Before using any of Tink APIs we need to initialize them.

If we need to use all implementations of all primitives in Tink, we can use the TinkConfig.register() method:


While, for example, if we only need AEAD primitive, we can use AeadConfig.register() method:


A customizable initialization is provided for each implementation, too.

4. Tink Primitives

The main objects the library uses are called primitives which, depending on the type, contains different cryptographic functionality.

A primitive can have multiple implementations:

Primitive Implementations
Deterministic AEAD AEAD: AES-SIV
Digital Signature ECDSA over NIST curves, ED25519
Hybrid Encryption ECIES with AEAD and HKDF, (NaCl CryptoBox)

We can obtain a primitive by calling the method getPrimitive() of the corresponding factory class passing it a KeysetHandle:

Aead aead = AeadFactory.getPrimitive(keysetHandle);

4.1. KeysetHandle

In order to provide cryptographic functionality, each primitive needs a key structure that contains all the key material and parameters.

Tink provides an object – KeysetHandle – which wraps a keyset with some additional parameters and metadata.

So, before instantiating a primitive, we need to create a KeysetHandle object:

KeysetHandle keysetHandle = KeysetHandle.generateNew(AeadKeyTemplates.AES256_GCM);

And after generating a key, we might want to persist it:

String keysetFilename = "keyset.json";
CleartextKeysetHandle.write(keysetHandle, JsonKeysetWriter.withFile(new File(keysetFilename)));

Then, we can subsequently load it:

String keysetFilename = "keyset.json";
KeysetHandle keysetHandle = File(keysetFilename)));

5. Encryption

Tink provides multiple ways of applying the AEAD algorithm. Let's take a look.

5.1. AEAD

AEAD provides Authenticated Encryption with Associated Data which means that we can encrypt plaintext and, optionally, provide associated data that should be authenticated but not encrypted.

Note that this algorithm ensures the authenticity and integrity of the associated data but not its secrecy.

To encrypt data with one of the AEAD implementations, as we previously saw, we need to initialize the library and create a keysetHandle:

KeysetHandle keysetHandle = KeysetHandle.generateNew(

Once we've done that, we can get the primitive and encrypt the desired data:

String plaintext = "baeldung";
String associatedData = "Tink";

Aead aead = AeadFactory.getPrimitive(keysetHandle); 
byte[] ciphertext = aead.encrypt(plaintext.getBytes(), associatedData.getBytes());

Next, we can decrypt the ciphertext using the decrypt() method:

String decrypted = new String(aead.decrypt(ciphertext, associatedData.getBytes()));

5.2. Streaming AEAD

Similarly, when the data to be encrypted is too large to be processed in a single step, we can use the streaming AEAD primitive:

KeysetHandle keysetHandle = KeysetHandle.generateNew(
StreamingAead streamingAead = StreamingAeadFactory.getPrimitive(keysetHandle);

FileChannel cipherTextDestination = new FileOutputStream("cipherTextFile").getChannel();
WritableByteChannel encryptingChannel =
  streamingAead.newEncryptingChannel(cipherTextDestination, associatedData.getBytes());

ByteBuffer buffer = ByteBuffer.allocate(CHUNK_SIZE);
InputStream in = new FileInputStream("plainTextFile");

while (in.available() > 0) {;


Basically, we needed WriteableByteChannel to achieve this.

So, to decrypt the cipherTextFile, we'd want to use a ReadableByteChannel:

FileChannel cipherTextSource = new FileInputStream("cipherTextFile").getChannel();
ReadableByteChannel decryptingChannel =
  streamingAead.newDecryptingChannel(cipherTextSource, associatedData.getBytes());

OutputStream out = new FileOutputStream("plainTextFile");
int cnt = 1;
do {
    cnt =;
} while (cnt>0);


6. Hybrid Encryption

In addition to symmetric encryption, Tink implements a couple of primitives for hybrid encryption.

With Hybrid Encryption we can get the efficiency of symmetric keys and the convenience of asymmetric keys.

Simply put, we'll use a symmetric key to encrypt the plaintext and a public key to encrypt the symmetric key only.

Notice that it provides secrecy only, not identity authenticity of the sender.

So, let's see how to use HybridEncrypt and HybridDecrypt:


KeysetHandle privateKeysetHandle = KeysetHandle.generateNew(
KeysetHandle publicKeysetHandle = privateKeysetHandle.getPublicKeysetHandle();

String plaintext = "baeldung";
String contextInfo = "Tink";

HybridEncrypt hybridEncrypt = HybridEncryptFactory.getPrimitive(publicKeysetHandle);
HybridDecrypt hybridDecrypt = HybridDecryptFactory.getPrimitive(privateKeysetHandle);

byte[] ciphertext = hybridEncrypt.encrypt(plaintext.getBytes(), contextInfo.getBytes());
byte[] plaintextDecrypted = hybridDecrypt.decrypt(ciphertext, contextInfo.getBytes());

The contextInfo is implicit public data from the context that can be null or empty or used as “associated data” input for the AEAD encryption or as “CtxInfo” input for HKDF.

The ciphertext allows for checking the integrity of contextInfo but not its secrecy or authenticity.

7. Message Authentication Code

Tink also supports Message Authentication Codes or MACs.

A MAC is a block of a few bytes that we can use to authenticate a message.

Let's see how we can create a MAC and then verify its authenticity:


KeysetHandle keysetHandle = KeysetHandle.generateNew(

String data = "baeldung";

Mac mac = MacFactory.getPrimitive(keysetHandle);

byte[] tag = mac.computeMac(data.getBytes());
mac.verifyMac(tag, data.getBytes());

In the event that the data isn't authentic, the method verifyMac() throws a GeneralSecurityException.

8. Digital Signature

As well as encryption APIs, Tink supports digital signatures.

To implement digital signature, the library uses the PublicKeySign primitive for the signing of data, and PublickeyVerify for verification:


KeysetHandle privateKeysetHandle = KeysetHandle.generateNew(SignatureKeyTemplates.ECDSA_P256);
KeysetHandle publicKeysetHandle = privateKeysetHandle.getPublicKeysetHandle();

String data = "baeldung";

PublicKeySign signer = PublicKeySignFactory.getPrimitive(privateKeysetHandle);
PublicKeyVerify verifier = PublicKeyVerifyFactory.getPrimitive(publicKeysetHandle);

byte[] signature = signer.sign(data.getBytes()); 
verifier.verify(signature, data.getBytes());

Similar to the previous encryption method, when the signature is invalid, we'll get a GeneralSecurityException.

9. Conclusion

In this article, we introduced the Google Tink library using its Java implementation.

We've seen how to use to encrypt and decrypt data and how to protect its integrity and authenticity. Moreover, we've seen how to sign data using digital signature APIs.

As always, the sample code is available over on GitHub.

Security bottom

I just announced the new Learn Spring Security course, including the full material focused on the new OAuth2 stack in Spring Security 5:

Generic footer banner
Comments are closed on this article!