Encryption as a Service in Action 
Tags: security, vault, encryption
Published: 2020-09-15 13:22:31

# TL;DR
After reading this blog post you will have an overview of why we need encryption as a service. Also, we will implement a PoC using Python and Hashicorp Vault to apply what we just learned. 

## Overview
Nowadays there are companies that deal with sensitive data such as banking or health information. This means that in the case of a security breach, the company could have legal, financial, and bad PR repercussions. As a result, there are some industry standard guidelines for storing this type of data (eg. Payment Card Industry Data Security Standard). For example, when it comes down to store Credit Card Data, organizations are required to encrypt the account numbers stored in databases. 

## Encryption in transit
While encryption at rest can be seamlessly be done by [using a cloud provider](https://aws.amazon.com/about-aws/whats-new/2019/02/amazon-dynamodb-adds-support-for-switching-encryption-keys-to-encrypt-your-data-at-rest/#:~:text=DynamoDB%20encrypts%20data%20using%20256,provided%20at%20no%20additional%20charge.&text=To%20learn%20more%2C%20see%20Amazon%20DynamoDB%20Encryption%20at%20Rest), handling the data when it’s moving around services can be tricky. Yes, you can use a secure communication channel (eg. TLS) between services, but this is not enough. One thing that could go wrong, even when using TLS, is logging sensitive information by mistake. This can happen even to [companies that are worth billions of dollars and with an awesome engineering team](https://www.zdnet.com/article/monzo-admits-to-storing-payment-card-pins-in-internal-logs/). 

## Handling encryption at the application level
This is possible since there are good cryptographic libraries out there. However, developers should do the implementation in a correct way (eg. not using deprecated ciphers). 

Furthermore, while a company might be running a [Majestic Monolith](https://m.signalvnoise.com/the-majestic-monolith/), chances are that you have other services that interact with your main service. This leads to questions such as:
* What happens if more than one service needs access to the sensitive data?
* Which service will handle encryption and/or decryption?
* How will encryption keys be handled, rotated, and distributed? 
* What happens if a key is leaked?

## Using a Key Management System
The solution is to protect sensitive data with a centralized key management system. This can be done using Hashicorp Vault, AWS KMS, Google CMK, etc. The idea is to delegate the responsibility of encryption and decryption to this service. Using a solution like Hashicorp Vault, allows you to encrypt and decrypt application data with an HTTPS API call. This means that:
* Data can be encrypted at rest
* Data is secured in Transit (TLS)
* Key handling and cryptographic implementations is taken care by Vault, not by developers
* More services could be added to interact with the sensitive data

## How it works?
![diagram](https://dev-to-uploads.s3.amazonaws.com/i/501zvzo8fmtl6grmxq62.png)

You can find the source code [here](https://github.com/shekodn/vault-encryption-as-a-service/releases/tag/v0.0.1).

As you can see we have 3 services: 

### API
This application allows you to create a "credit card" with a name and a PAN (primary account number). All data is stored encrypted and API doesn't know anything about the type encryption used or how to decrypt it.

### Card Processor
This service process the credit cards we have created so far according to the business logic. Just as with the API, it doesn't know anything about encryption or decryption. 

### Vault
This service handles the encryption done by the API and the decryption done by the Card Processor Service. If a new service is added in the future, everything regarding encryption or decryption would still depend 100% on Vault. The best part is that no changes would be made in the other services. 

## How to Run
Just do `docker-compose up --build` and you should be ready to go.

## Configuration
Run the following script:
```sh
./bin/vault.sh
```
3 things just happened:

1. The transit Secrets Engine was enabled. This tells vault to **not** save data and turn on the __encryption as a service__ functionality.
2. The root token was created. This one is used by our apps to encrypt and decrypt.
3. A symmetric key was generated. This one is used to encrypt and decrypt the respective data.

Grab the `token` from the output value and put it as `VAULT_TOKEN` env var in:
- `app/settings.py`
- `card_processor.py`

## Using the credit card API
Send a POST request to `http://localhost:8000/credit-cards/` with the following body:
```
{
    "name":"Carlos Gardel",
    "pan":"4539296620131157"
}
```

If everything went well you should receive the the encrypted Primary Account Number (pan).
```
{
    "name": "Carlos Gardel",
    "pan": "vault:v1:JnC8pS/zmHhHPGd7dk5eCGilnUi8odvRIBP9Z+rBmMLAWXJ/dgYqGAU4MTk="
}
```

If you do a GET request to `http://localhost:8000/credit-cards/` you will still see the encrypted PAN field.


## Using the card processor service

Now let's processes the card using the `card_processor.py` service.

```python
python3 card_processor.py
```

We should be able to see that the card was successfully decrypted and processed:
```
Processing CARLOS GARDEL card with number 4539296620131157
```

## Rotating Keys
To minimize the risk if a key is leaked, let's tell Vault to rotate it. 
```sh
./bin/rotate.sh
```

If we do another POST request to the credit card application, we will see that we used `v2` to perform the encryption. 
```
{
    "name": "Aníbal Troilo",
    "pan": "vault:v2:vdIlXggLzrM4n5Xlzxh6a/xpmd7yz/F9MsoifuR/kmOodGKV5wPaWvMMiEw="
}
```

Let's do a GET and see our different encryption key version (v1 and v2):
```
[
    {
        "name": "Carlos Gardel",
        "pan": "vault:v1:JnC8pS/zmHhHPGd7dk5eCGilnUi8odvRIBP9Z+rBmMLAWXJ/dgYqGAU4MTk="
    },
    {
        "name": "Aníbal Troilo",
        "pan": "vault:v2:vdIlXggLzrM4n5Xlzxh6a/xpmd7yz/F9MsoifuR/kmOodGKV5wPaWvMMiEw="
    }
]
```

Let's go back to the `card_processor.py` service. Now we are able to decrypt and process both entries. One was decrypted with `v1` and the other one with `v2`.
```
Processing CARLOS GARDEL card with number 4539296620131157
Processing ANÍBAL TROILO card with number 2720997130887021
```


Voilà. Encryption keys and rotation were handled by Vault.


## Further considerations 
### Are we production ready yet? 
I don't think so. 
* We are using a root token
* TLS and communication between Vault and services is yet to be implemented
* Vault needs to be sealed
* We need to make sure that the Vault service is reliable (did someone say K8s?)

### Open Source vs Cloud Provider Solution 
Do you want to deal with a self-hosted open source solution or would you rather deal with your respective cloud provider’s solution? 

# Wrapping It Up
If you want to discuss or challenge the implementation I’m happy to do so. Just leave a comment or find me on [twitter](https://twitter.com/sergiodn_).


# References:
- https://m.signalvnoise.com/the-majestic-monolith/
- https://blog.deurainfosec.com/credit-card-primary-account-number-and-encryption/
- https://www.zdnet.com/article/monzo-admits-to-storing-payment-card-pins-in-internal-logs/
- https://aws.amazon.com/about-aws/whats-new/2019/02/amazon-dynamodb-adds-support-for-switching-encryption-keys-to-encrypt-your-data-at-rest/