How to use envelope encryption for a practical use case

Envelope encryption is a way to encrypt data in a secure, yet performant.

Before diving into the details, I'm going to use a use case that will help us understand why this method of envelope encryption exists, how its better than other methods etc.

Example use case for envelope encryption

You're working on a secure journalling app. One of the requirements is that all the data is encrypted at rest. Users would like to see all their previous journal entries on a single page. Remember, these entries need to be encrypted when they're stored in our persistence layer.

A quick aside on symmetric Vs asymmetric data encryption

Symmetric encryption uses the same key to encrypt and decrypt data

Asymmetric encryption uses a keypair - a public key and a private key. The public key encrypts, and the private key decrypts. Slower than symmetric encryption. See this resource that explains asymmetric encryption in HTTPS.

Back to envelope encryption

The elements involved in envelope encryption are:

  • Data encryption key (DEK)
  • Key encryption key (KEK)

How it works

We first generate a random key (in code) that we will use to encrypt our data. This key will be used as a symmetric key to encrypt single journal entries.

What we have now is our encrypted data, and the symmetric key (our DEK) used to encrypt this data. Anyone with access to this DEK will be able to decrypt this data.

Next, we encrypt our data encryption key with yet another key. This new key is our key encryption key (KEK). Usually, a more secure (slower) algotithm is used to encrypt this our DEK.

The encrypted journal entry, and the encrypted data encryption key (DEK) can now be stored together in our store / database / persistent layer.

When this journal entry needs to be displayed, we can fetch our encrypted data, our encrypted data encryption key (DEK). Then, we decrypt the DEK, and use the decrypted DEK to decrypt the journal entry before sending it to the client to read.

How this is more efficient

Usually, you'd use a cloud service provider's managed key service (eg: Cloud key management (google) or AWS key management service) to encrypt some data.

A KMS will usually have a limit on the size of data it can encrypt (usually around 4Kb). They're not designed to encrypt bulk data like a journal entry.

Also, if we did use a KMS to encrypt an entire journal entry, we'd need to send the entire journal entry over the network any time we wanted to encrypt or decrypt this data, which would have an adverse effect on latency of your system. You'd also be rate-limited quickly. Imagine a scenario where our system has 100 cuncurrent users who each have 50 journal entries. We'd be hitting that rate-limit fairly quick, which will then result in our user's not being able to load their journal entries. There are also cost implications of the amount of data being encrypted by our KMS.

It doesn't matter how secure your app is if your users can't use it.

By only sending our data encryption key (DEK) over the network, we're minimising the data our KMS system needs to process, keeping latency per entry low.

Once we've made the faster call to decrypt our encrypted DEK, its a fairly quick local operation (i.e. does not require a network) to decrypt our journal entry data.

We could further make it more efficient by creating a DEK per user, so we'd only need to make 1 network call to KMS, making the entire operation more efficient. Yes, it comes with some compromises that you'd need to weigh it against.

Some other advantages of using envelope encryption in this context:

  • The KMS keys never interact with sensitive user data
  • Our data encryption keys (DEK) can be rotated flexibly. We could run a batch script to decrypt our data, and re-encrypt with a new DEK, without needing to rotate anything in our KMS service

The source code for this website can be found here under an MIT license