Author: Jacob Mammoliti


Using Vault’s Encryption as a Service Secrets Engine: Transit

HashiCorp Vault offers a Secret Engine, Transit, that is able to encrypt data as it is passed to it. Vault does not store any of the data it encrypts, it is merely a tool to help remove the burden of having to encrypt data in-code and provide a streamlined & universal way to encrypt data. The Transit Secret Engine takes in base64 encoded data therefore it can handle any data that can be encoded in that format, such as plaintext or images.

In this blog, I’ll go through the steps to set up the Secret Engine in Vault, set up a demo encryption key, validate the Secret Engine is working, and finally go through an example using a Python script and the HVAC Python library.

Environment

For this blog, I’ll stand up a Vault server in dev mode on my local machine. Of course, this method of using Vault should not be used for anything more than quick testing. Everything is stored in memory, so once you stop the server, all of your configurations are gone.

Open a terminal window and start up a dev server. Vault will come up unsealed and will provide an unseal key and root token. Since we don’t have any other log-in method or ACL policies configured, we will use the root token.

Note: In a production environment, a root token should only be used for initial configuration. Once done, it should be revoked.

~]$ vault server --dev
==> Vault server configuration:

             Api Address: http://127.0.0.1:8200
                     Cgo: disabled
         Cluster Address: https://127.0.0.1:8201
              Listener 1: tcp (addr: "127.0.0.1:8200", cluster address: "127.0.0.1:8201", ..
               Log Level: info
                   Mlock: supported: false, enabled: false
           Recovery Mode: false
                 Storage: inmem
                 Version: Vault v1.3.1+prem
...

Transit Secret Engine Setup

Once the Vault server is up, we can log into it with the root token and enable the Transit Secrets Engine. When we create an encryption key, the default type is AES-256 wrapped with GCM using a 96-bit nonce size AEAD. If there is a desire to use another encryption method, the supported types can be found here.

# export environment variables
~]$ export VAULT_ADDR="http://127.0.0.1:8200"

# log into Vault
~]$ vault login
Token (will be hidden): s.R3f4udg0rDs4kaVslaUKUM4S
Success! You are now authenticated. The token information displayed below
is already stored in the token helper. You do NOT need to run "vault login"
again. Future Vault requests will automatically use this token.

Key                  Value
---                  -----
token                s.R3f4udg0rDs4kaVslaUKUM4S
token_accessor       jQyauBMcWGptHm7PZgjAoD7C
token_duration       ∞
token_renewable      false
token_policies       ["root"]
identity_policies    []
policies             ["root"]

# enable the Transit Secrets Engine
~]$ vault secrets enable transit
Success! Enabled the transit secrets engine at: transit/

# create an encryption key using aes-256 that will be used to encrypt the data
~]$ vault write -f transit/keys/demo-key-aes256
Success! Data written to: transit/keys/demo-key-aes256

# read the key we just created to verify configuration
~]$ vault read transit/keys/demo-key
Key                       Value
---                       -----
allow_plaintext_backup    false
deletion_allowed          false
derived                   false
exportable                false
keys                      map[1:1578503999]
latest_version            1
min_available_version     0
min_decryption_version    1
min_encryption_version    0
name                      demo-key
supports_decryption       true
supports_derivation       true
supports_encryption       true
supports_signing          false
type                      aes256-gcm96

Testing Out the Encryption

Now that the Secrets Engine is configured, we can test it with some sample data. For this example, we’ll encrypt a sample credit card number and then decrypt it to show the full workflow. Remember, Vault is able to encrypt any object as long as it is base64 encoded.

# encrypt a sample credit card number that is first base64 encoded
~]$ vault write transit/encrypt/demo-key \
    plaintext=$(base64 <<< "1111 1111 1111 1111")
Key           Value
---           -----
ciphertext    vault:v1:J842Njb0V9OLTXgF/kalKT/icVRJrDquVPgi5e...

# decrypt the above ciphertext to get the credit card number back
~]$ vault write transit/decrypt/orders \
    ciphertext="vault:v1:J842Njb0V9OLTXgF/kalKT/icVRJrDquVPgi5e..."
Key          Value
---          -----
plaintext    NDExMSAxMTExIDExMTEgMTExMQo=

# decode the base64 plaintext
~]$ base64 --decode <<< "NDExMSAxMTExIDExMTEgMTExMQo="
1111 1111 1111 1111

Python Example

Being able to encrypt one off pieces of data is great, but where the Transit Secrets Engine becomes more valuable is when it can be used within an application. By levering the Secrets Engine, this takes off the burden of having to incorporate some sort of encryption into the code and is now shifted to let Vault handle it. This allows developers to purely focus on the development of their application.

Below is sample Python script to help showcase Transit in code utilizing the hvac python library for Vault which is super useful since it has some pre-built logic into it for authentication, Vault status information, and also adds another layer of abstraction from the HTTP API.

This app is quite cumbersome in that it just shoots out the inputted text you put in, but it does highlight how to use both the Transit Encryption Engine along with hvac to show how you can use it within your own application.

Note: the hvac python library needs to be installed and can be easily done via pip pip install hvac.

~]$ cat << EOF > app2.py
import base64
import hvac
import os

client = hvac.Client(url=os.environ['VAULT_ADDR'],
                     token=os.environ['VAULT_TOKEN'])

print ("Enter in your credit card number: ")
cc_number = input()

encodedBytes= base64.b64encode(cc_number.encode("utf-8"))

encrypt_data_response = client.secrets.transit.encrypt_data(
    name = 'demo-key',
    plaintext = str(encodedBytes, "utf-8")
)

ciphertext = encrypt_data_response['data']['ciphertext']

print('Encrypted plaintext ciphertext is: {cipher}'.format(cipher=ciphertext))

decrypt_data_response = client.secrets.transit.decrypt_data(
    name='demo-key',
    ciphertext=ciphertext,
)

plaintext = decrypt_data_response['data']['plaintext']
print('Decrypted plaintext is: {text}'.format(text=plaintext))
print('Base64 decoded plaintext is: {text}'\
    .format(text=str(base64.b64decode(plaintext), "utf-8")))
EOF

~]$ python app.py
Enter in your credit card number: 
1111 1111 1111 1111
Encrypted plaintext ciphertext is: vault:v1:Z58OkKCYgx9XmB50CYKSr3oxkJBAinQkBkvEXEbgK9wLEjKf6YaC0rtDr1/a7N0=
Decrypted plaintext is: MTExMSAxMTExIDExMTEgMTExMQ==
Base64 decoded plaintext is: 1111 1111 1111 1111

Conclusion

The Transit Secrets Engine is pretty powerful since it shifts the responsibility of encryption from the developers to the administrators of Vault. This way, developers can focus on writing solid code and expect that when they send information to Vault, the result they get back will meet a standard level of encryption that is set by the Vault administrators. Developers can now store this encrypted text in a database with the ease-of-mind that their data is in fact sitting on the database fully encrypted.

If you’re interested in learning more about the Transit Secrets Engine or Vault in general, reach out to us and we’d love to have a conversation around the tool! I’m constantly working on new labs with Vault and the rest of the HashiCorp stack.

Tagged:



//comments


//blog search


//other topics