Demystify encryption by taking a *medium* dive and building your very own encryption engine in Elixir. In this post, we'll build a light-weight Elixir library that leverages Erlang's `:crypto`

to encrypt/decrypt sensitive data, and learn a little more about encryption along the way.

## Overview

Our library will be able to do the following:

- Generate a secret key that we will use to encrypt/decrypt strings of text
- Use the secret key to encrypt and base64 encode a string
- Use the secret key to decrypt and base64 decode a string

Let's get started!

## Set Up

We'll generate a new project:

```
mix new encrypt
```

Our project doesn't need many dependencies. We're going to use Erlang to do the encryption heavy lifting here, instead of relying on an existing encryption package. We will add Ex Doc though so that we can document our libary:

```
# mix.exs
defp deps do
[
{:ex_doc, "~> 0.19", only: :dev, runtime: false}
]
end
```

We'll install it with `mix deps.get`

Now we're ready to define our encryption module!

## The Encryption Module

### Generating the Secret Key

In order to encrypt/decrypt plain text, we need a secret key. Let's give our module the ability to generate such a key:

```
# lib/encrypt.ex
defmodule Encrypt do
@moduledoc """
Documentation for Encrypt.
"""
@doc """
`generate_secret`
Generates a random base64 encoded secret key.
"""
def generate_secret do
:crypto.strong_rand_bytes(16)
|> :base64.encode
end
```

*Note that we've added some documentation using @doc.*

Our secret generation code is pretty simple. First we use `:crypto.strong_rand_bytes/1`

to generates a binary composed of 16 random bytes. Then, we use Erlang's `:base64.encode/1`

to base 64 encode that binary. This gives us a string that we can store for later use in a file or environment variable.

```
Encrypt.generate_secret
# => "EC7+ItmG04KZzcS1Bg3o1g=="
```

Now that we have a secret key, we can use it to encrypt plaintext.

### Encrypting Plaintext

We'll use `:crypto.block_encrypt/4`

to enact our encryption. The `block_encrypt`

function needs the following arguments:

- The mode of encryption we want to implement
- The secret key we want to use for encryption
- The initialization vector
- The string we want to encrypt.

The `block_encrypt`

function returns a tuple:

```
{ciphertext, ciphertag}
```

The ciphertext is the encrypted version of our original plaintext. The ciphertag is the "message authentication code", or MAC. This is a

...short piece of information used to authenticate a message—in other words, to confirm that the message came from the stated sender and has not been changed. The MAC value protects both a message's data integrity as well as its authenticity, by allowing verifiers to detect any changes to the message content.

We'll revisit `block_encrypt/4`

and take a closer look at the arguments it requires in a bit. First, let's define *our* encryption function to take in two arguments: the value we want to encrypt and the secret key with which we want to encrypt it.

```
defmodule Encrypt do
...
def encrypt(val, key) do
# coming soon!
end
end
```

Now we're ready to craft the arguments we need to pass to `:cryptol.block_encrypt/4`

#### Specifing Encryption Mode

First off, we need to indicate the encryption mode. We'll use the Advanced Encryption Standard Galois Counter Mode which we can specify to `block_encrypt`

as the atom: `:aes_gcm`

```
defmodule Encrypt do
...
def encrypt(val, key) do
mode = :aes_gcm
end
end
```

#### Decoding the Secret Key

Next up, we need to pass the encryption key to this function call. However, we base-64 encoded our binary key in our `generate_secret`

function. `block_encrypt/4`

, however, needs the key in its binary form. So we need to base-64 *decode* the key:

```
defmodule Encrypt do
...
def encrypt(val, key) do
mode = :aes_gcm
secret_key = :base64.decode(key)
end
end
```

#### Setting the Initialization Vector

The third argument we need to pass `block_encrypt/4`

is the initialization vector. The initialization vector is a random, fixed-size input that is used to enable block encryption to securely encrypt plaintext of any size.

Why do we need an initialization vector?

Block encryption will encrypt data of an arbitrary length by splitting that data into blocks, each matching the block cipher's size. Then, it will encrypt each block separately using the same secret key. This is NOT secure. Plaintext blocks of equal size get transformed into equal ciphertexts *every time*. In other words, every time you encrypt the same string, you end up with the *same ciphertext*. This gives any bad actors unacceptable insight into the nature of the encrypted data.

Using an initialization vector solves this problem by randomizing the input data.

Every time we perform encryption, a different random set of bytes is mixed with the first block of input data. Then the ciphertext from the first block's encryption is added to the second block, the ciphertext from the second block is added to the third and so on. This provides **semantic security**. Every time we encrypt the same string, we end up with *different ciphertext*, thereby ensuring that an attacker can't make any conclusions about the original plaintext by observing the encrypted ciphertext.

We'll generate an initialization vector using the same technique we used to kick off the generation of our secret key, the `:crypto.strong_rand_bytes/1`

function.

```
defmodule Encrypt do
...
def encrypt(val, key) do
mode = :aes_gcm
secret_key = :base64.decode(key)
iv = :crypto.strong_rand_bytes(16)
end
end
```

#### Specifying the Encryption Value Tuple

We need to pass the value to be encrypted into `block_encrypt/4`

via a tuple:

```
{AAD, Plaintext, TagLength}
```

This will tell `block_encrypt/4`

to take our plaintext, and using the Authenticated Encryption with Associated Data mode (AEAD), encrypt it with the method specified in the first argument, `:aes_gcm`

. We'll specify an AEAD mode of `"AES256GCM"`

which communicates the encryption mode in three parts:

- AES: Advanced Encryption Standard.
- 256: "256 Bit Key"
- GCM: "Galois Counter Mode"

Let's put it all together:

```
defmodule Encrypt do
@aad "AES256GCM"
...
def encrypt(val, key) do
mode = :aes_gcm
secret_key = :base64.decode(key)
iv = :crypto.strong_rand_bytes(16)
{ciphertext, ciphertag} = :crypto.block_encrypt(mode, secret_key, {@aad, to_string(val), 16})
end
end
```

*Note: We set the AEAD as a module attribute since we will reuse it when we perform our decryption*.

#### Returning the Encrypted Data

In order to be able to *decrypt* our encrypted data, we need to use not just the ciphertext, but the ciphertag and initialization vector. So, our function will return a concatenation of these three pieces of data:

```
defmodule Encrypt do
@aad "AES256GCM"
...
def encrypt(val, key) do
mode = :aes_gcm
secret_key = :base64.decode(key)
iv = :crypto.strong_rand_bytes(16)
{ciphertext, ciphertag} = :crypto.block_encrypt(mode, secret_key, {@aad, to_string(val), 16})
iv <> ciphertag <> ciphertext
end
end
```

Now we have something that works like this:

```
secret = Encrypt.execute_action([action: "generate_secret"])
=> "+fmYuFICS1a5ZVxesmRrpQ=="
Encrypt.encrypt("This is so cool", secret)
=> <<248, 28, 213, 4, 12, 55, 173, 193, 119, 45, 103, 255, 207, 249, 240, 198, 113,
221, 79, 255, 107, 234, 150, 115, 0, 120, 174, 37, 24, 140, 188, 25, 123, 189,
210, 87, 37, 11, 52, 29, 150, 120, 197, 99, 209, 242, 165>>
```

We can see that this returns a binary. To make it easier to store our encrypted data, we'll take the next step of transforming this binary into a base-64 encoded string.

```
defmodule Encrypt do
@aad "AES256GCM"
...
def encrypt(val, key) do
mode = :aes_gcm
secret_key = :base64.decode(key)
iv = :crypto.strong_rand_bytes(16)
{ciphertext, ciphertag} = :crypto.block_encrypt(mode, secret_key, {@aad, to_string(val), 16})
iv <> ciphertag <> ciphertext
|> :base64.encode
end
end
```

Now we end up with the return value:

```
ciphertext = Encrypt.encrypt("This is so cool", secret)
=> "BVb85x/NaL4lbZr2ZD8E3Q1SsfxLo9mAOiH+GGXCH6YH7QGHf52AfzsAOc46enI="
```

### Decrypting Ciphertext

Now we're ready to build our decryption function. For this, we'll rely on `:crypto.block_decrypt/4`

First, let's define our `decrypt`

function to take in an argument of the ciphertext and the secret key that was used to encrypt it.

```
defmodule Encrypt do
def decrypt(ciphertext, key) do do
# coming soon!
end
end
```

The four arguments we need to give `block_decrypt`

are:

- The mode of encryption
- The secret key
- The initialization vector
- The ciphertext to decrypt

We already know how construct our first two arguments––just exactly as we did for our `block_encrypt`

function call:

```
defmodule Encrypt do
def decrypt(ciphertext, key) do do
mode = :aes_gcm
secret_key = :base64.decode(key)
end
end
```

But where do we get our initialization vector? We need to grab the same initialization vector that we used to enact our encryption. Recall that our `encrypt`

function returned the base64 encoded concatenation of our initialization vector, our ciphertext and our ciphtertag in the format:

```
iv <> ciphertext <> ciphertag
```

We we'll use pattern matching to extract these components from the encrypted and encoded value we passed into `decrypt`

:

```
defmodule Encrypt do
def decrypt(ciphertext, key) do do
mode = :aes_gcm
secret_key = :base64.decode(key)
ciphertext = :base64.decode(ciphertext)
<<iv::binary-16, tag::binary-16, ciphertext::binary>> = ciphertext
end
end
```

This way, we expose the initialization vector, `iv`

, the `ciphertext`

that represents the encrypted plaintext, `ciphertext`

, *and* the ciphertag, `tag`

––all of which we need in order to perform our decryption.

Now that we have all of these components available to us, we're ready to invoke `block_decrypt`

:

```
defmodule Encrypt do
def decrypt(ciphertext, key) do do
mode = :aes_gcm
secret_key = :base64.decode(key)
ciphertext = :base64.decode(ciphertext)
<<iv::binary-16, tag::binary-16, ciphertext::binary>> = ciphertext
:crypto.block_decrypt(mode, secret_key, iv, {@aad, ciphertext, tag})
end
end
```

Note that the fourth argument is a tuple that specifies the encryption mode, the cipher text and the tag.

We can use our function like this to return the decrypted plaintext:

```
Encrypt.decrypt(ciphertext, secret)
=> "This is so cool"
```

Yes, it *is* so cool.

## Next Steps

At The Flatiron School, we built our encryption engine into a command line executable using escript and released it as a Hex package. You can check out the code for this project here and install the package:

```
mix escript.install hex encrypt
```

## Conclusion

Building this library provided me with some insight into the encryption process, and it made me really appreciate Elixir's ability to use any native Erlang functions. It made it easy to produce such a lightweight and extensible library, and Erlang's documentation helped me to finally peek under the hood and understand a bit about how encryption works. I hope it did the same for you. Happy coding!