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:
<dependency> <groupId>com.google.crypto.tink</groupId> <artifactId>tink</artifactId> <version>1.2.2</version> </dependency>
Though we could have used Gradle instead:
dependencies { compile 'com.google.crypto.tink:tink:latest' }
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:
TinkConfig.register();
While, for example, if we only need AEAD primitive, we can use AeadConfig.register() method:
AeadConfig.register();
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 |
---|---|
AEAD | AES-EAX, AES-GCM, AES-CTR-HMAC, KMS Envelope, CHACHA20-POLY1305 |
Streaming AEAD | AES-GCM-HKDF-STREAMING, AES-CTR-HMAC-STREAMING |
Deterministic AEAD | AEAD: AES-SIV |
MAC | HMAC-SHA2 |
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 = CleartextKeysetHandle.read(JsonKeysetReader.withFile(new 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:
AeadConfig.register(); KeysetHandle keysetHandle = KeysetHandle.generateNew( AeadKeyTemplates.AES256_GCM);
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:
AeadConfig.register(); KeysetHandle keysetHandle = KeysetHandle.generateNew( StreamingAeadKeyTemplates.AES128_CTR_HMAC_SHA256_4KB); 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) { in.read(buffer.array()); encryptingChannel.write(buffer); } encryptingChannel.close(); in.close();
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 { buffer.clear(); cnt = decryptingChannel.read(buffer); out.write(buffer.array()); } while (cnt>0); decryptingChannel.close(); out.close();
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:
TinkConfig.register(); KeysetHandle privateKeysetHandle = KeysetHandle.generateNew( HybridKeyTemplates.ECIES_P256_HKDF_HMAC_SHA256_AES128_CTR_HMAC_SHA256); 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:
TinkConfig.register(); KeysetHandle keysetHandle = KeysetHandle.generateNew( MacKeyTemplates.HMAC_SHA256_128BITTAG); 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:
TinkConfig.register(); 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.