Vault’s Transit secrets engine lets you encrypt and decrypt data without ever exposing the plaintext to Vault itself.

Here’s Transit in action, encrypting and decrypting a secret:

# First, enable the Transit secrets engine
vault secrets enable transit

# Then, create an encryption key named "my-key"
vault write transit/keys/my-key

# Now, encrypt some plaintext data
vault write transit/encrypt/my-key plaintext=$(base64 <<< "my super secret data")

# You'll get back ciphertext. Let's decrypt it.
vault write transit/decrypt/my-key ciphertext=<the ciphertext from above>

The problem Transit solves is managing encryption keys. Traditionally, you’d generate an AES key, store it somewhere (often in a config file or another secret store), and then use that key to encrypt your data. This creates a chicken-and-egg problem: where do you securely store the key that protects your other secrets? Transit bypasses this by keeping the keys inside Vault’s encrypted storage, and the encryption/decryption operations happen within Vault’s secure boundary. You send plaintext to Vault, it encrypts it, and returns ciphertext. You send ciphertext back, it decrypts it, and returns plaintext. Vault never exposes the raw encryption key itself.

Internally, Transit uses AES-GCM by default. When you create a key, Vault generates a strong AES key and encrypts it using Vault’s own root encryption key. This encrypted key is then stored in Vault’s storage backend. When you request an encryption or decryption, Vault retrieves the encrypted key, decrypts it using the root key (which is itself protected by Vault’s Seal), performs the cryptographic operation, and then discards the plaintext key from memory. The ciphertext returned includes an initialization vector (IV) and a version tag for the key used, allowing Transit to use the correct key for decryption.

The primary lever you control is the key name. You can create multiple named keys within a single Transit engine, allowing you to segregate encryption operations by purpose or sensitivity. For instance, you might have transit/encrypt/user-data-key and transit/encrypt/api-keys-key. You can also configure key rotation policies, specifying how often Transit should generate a new version of a key. This is crucial for security, as it limits the amount of data encrypted with any single key version.

You can also perform HMAC operations for integrity checks, generate random data, and even perform convergent encryption. Convergent encryption is particularly interesting: it means that the same plaintext input will always produce the same ciphertext output, regardless of when or where you encrypt it, as long as you use the same key. This is achieved by deriving the encryption key from the plaintext itself, concatenated with a salt. This is useful for deduplication or for scenarios where you need to quickly check if you’ve already encrypted a piece of data without storing a separate lookup table.

When you delete a Transit key, Vault doesn’t immediately erase the encrypted data. Instead, it marks the key as "destroyed" and enters a configurable destruction delay period. During this delay, the key can still be used for decryption, allowing you to recover any data encrypted with it. After the delay expires, the key is permanently deleted, and any data encrypted with it becomes irrecoverable.

The next concept to explore is using Transit to manage application-level encryption and decryption directly within your applications, often by integrating Vault’s API.

Want structured learning?

Take the full Vault course →