Skip to main content

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

By July 15, 2021No Comments

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

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.


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:                      Cgo: disabled          Cluster Address:               Listener 1: tcp (addr: "", cluster address: "", ..                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=""  # 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 > 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 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


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.