Envelope Encryption with Google Tink and GCP Key Management (with example).

piotr szybicki
5 min readSep 24, 2018

--

Cryptography is a subject that was forgotten 5 minutes after we passed an exam in collage. Companies these days have teams that design security frameworks. Leaving developers that code the business functionality without that burden. With an advent of ‘off premise computing’ (cloud, blockchain etf..) that has to change. As our data no longer sits safely behind the firewall that is fully controlled by the organization.

At a very least we need to know what algorithm to use for digital signature, encryption, stream encryption etc. Just recently (and this is a direct cause for this article) we had to use outside provider to host our db clusters (turns out it is cheaper that way). Of course we couldn’t store plain text (even if guy’s there said they use, by default, encryption for data at rest) because people are lying, or, more often, are just mistaken.

And this is where meat of this article begins. Let’s analyze a simplest case we have a JSON document and we want store it in a data structure let’s say hashmap. But we can’t trust the demon that owns that hashmap at all. If we put plain text json in there, that is game over for us. The demon can read the information. Ok, we can use encryption, great now we store cypher text the demon can look at it all he wants and gets nothing. Assuming that that we use encryption that is impossible to solve by any other means but a brute force attack and the key is long enough for the daemon not to bother. But wait, assume that we use single key to encrypt the all documents ( not unreasonable assumption :)) the demon can read the documents but he can swap records around and we wouldn’t know any better after all we can fech and decrypt the document no problem.

We could use hasing algorithm. Hash document and use that as key in a map. That can work (and we have a cryptographic protocol :)) fetch document using a key, decrypt it, calculate the hash and compare it to the key if they mach we can say that record was not tampered with. However this approach will work only if you can guarantee that every document you try to store is unique.

You see every good cryptographic hash function has three properties (there are two more but forget them for now)

  1. Determinizm (same input gives same output)
  2. Non-invertibility or one-way operation (can’t calculate input based on output)
  3. Collision free

And number 1 and 3 are the one that makes this work and it doesn’t at the same time. I can calculate the hash of a document and I get the same result every time (determinizm) but if i have exact looking document submitted by two different user it will cause a collision (probably undetected) and one of the entries in a map will be overwritten. There is ways around that of course for example we can add some random number to the document and that will ensure that we get a different hash. That however is not necessary and a bad practice as we are mixing business data with some technicalites. Plus we have to be mindful what hash function do we use. As some are more valuable than the other (just as a side note):

What I end up using was one of the cryptographic primitives provided by the tink (the crypto library developed by Google) called AEAD (Authenticated Encryption with Associated Data). It gives us three properties that we need:

  • Confidentialyty (not undestandable)
  • Integrity (not modified)
  • Authenticity (not switched)

They form acronym CIA :). Dispite the fact that authenticty is special case of integratiy.

source: https://upload.wikimedia.org/wikipedia/commons/thumb/b/b9/Authenticated_Encryption_EtM.png/220px-Authenticated_Encryption_EtM.png

As you can see the Ciphertext and MAC (Message Authentication Codes) are stored together. And upon decryption two values have to be provided order to decrypt.

Lets get to work

First we have to create a key ring and a master key, that will be used to encrypt child keys and store them in bucket (I had to create the bucket as well).

gcloud kms keyrings create my-app-keyring --location global
gcloud kms keys create master-key --location global --keyring my-app-keyring --purpose encryption
gsutil mb -p {project_name} -c multi_regional gs://keys-for-my-app-233/

Then create a service account. And add needed permisions. I needed to ba able to access a KMS (Key management system) and GCS (Google Cloud Storage)

gcloud iam service-accounts create tinkexamplegcloud projects add-iam-policy-binding {name_of_your_project} --member "serviceAccount:tinkexample@{name_of_your_project}.iam.gserviceaccount.com" --role "roles/
cloudkms.cryptoKeyEncrypterDecrypter
gsutil iam ch "serviceAccount:tinkexample@name_of_your_project.iam.gserviceaccount.com:legacyBucketReader,legacyBucketWriter" gs://keys-for-my-app-233"

And generate a file, when you run your application you have to specify the location of that file using env variable (GOOGLE_APPLICATION_CREDENTIALS):

gcloud iam service-accounts keys create some_file_name.json --iam-account tinkexample@name_of_your_project.iam.gserviceaccount.com

So to create a data encryption key you have to run following code. (Full source code in the section below.)

// AES128 <- advance encyption standart (128 bit key lenght)
// GCM <- Galois/Counter Mode, more on that leater

KeysetHandle keysetHandle = KeysetHandle.generateNew(AES128_GCM);
//connect to the Google Key Storage
KmsClient client = new GcpKmsClient().withDefaultCredentials();
Aead aead = AeadFactory.getPrimitive(keysetHandle);
//fetch the master key
final Aead msterKey = client.getAead(MASTER_KEY_LOCATION);
keysetHandle.write(withPath("./key.json"), msterKey);

As a side not in the code sample provided in git hub I store the key in the Google Cloud Storage that is why when granting permission Ialso had to grant permission to the bucket. But here for simplicity I encrypt the key using master key and save it to hard drive.

Now in order to encrypt some data:

public static void encrypt() throws GeneralSecurityException, IOException {
TinkConfig.register();
KmsClient client = new GcpKmsClient().withDefaultCredentials();
final Aead msterKey = client.getAead(MASTER_KEY_LOCATION);
KeysetHandle keysetHandle = KeysetHandle.read(JsonKeysetReader.withPath("./key.json"), msterKey);
Aead aead = AeadFactory.getPrimitive(keysetHandle);

String jsonData = "some secret i want protecting";
final String keyInMap = "data to authenticate the message";

final byte[] encrypt = aead.encrypt(jsonData.getBytes(), keyInMap.getBytes());
// store the encrypted data with uuid key as key in that map but any other id that is unique in your domain will work just as well
FileUtils.writeByteArrayToFile(new File("./encrypted.data"), encrypt);
System.out.println(keyInMap);
}

End to decrypt it:

public static void decrypt() throws GeneralSecurityException, IOException {
TinkConfig.register();
KmsClient client = new GcpKmsClient().withDefaultCredentials();
final Aead msterKey = client.getAead(MASTER_KEY_LOCATION);
KeysetHandle keysetHandle = KeysetHandle.read(JsonKeysetReader.withPath("./key.json"), msterKey);
Aead aead = AeadFactory.getPrimitive(keysetHandle);

final byte[] encrypted = FileUtils.readFileToByteArray(new File("./encryptped.data"));
final byte[] decrypt = aead.decrypt(encrypted, "data to authenticate the message".getBytes());
System.out.println(new String(decrypt));
}

Code example

Bellow is the link to my repo. Containing the sample project. It might seem a little bit complicated but that is my new year resolution. To write a code in this examples that I consider production ready. There are a lot of junior developer that often copy thouse solutions blindly.

--

--

piotr szybicki
12 developer labors

Piotr Szybicki’s, Programmer, Java Developer, ML Entusiast