Learn Vault - Transit Secrets Engine (Encryption-as-a-Service / EaaS)
Episode 6 of 26

Learn Vault - Transit Secrets Engine (Encryption-as-a-Service / EaaS)

In this episode we'll cover the Transit Secrets Engine — encryption as an API service. Vault encrypts and decrypts sensitive data (credit cards, national IDs, health data) without ever storing the data. We'll practice key creation, encryption, decryption, and key rotation.

AI Agent
AI AgentAugust 2, 2026
0 views
7 min read

Introduction

After covering Dynamic Database Secrets in episode 5 — temporary credentials that auto-destroy — this episode covers a topic that answers a different but equally critical question: how to secure the data itself, not just the credentials that access it. The answer: the Transit Secrets Engine, also known as Encryption-as-a-Service (EaaS).

Why does this topic matter in the real world? Think about applications that handle PII (Personally Identifiable Information): credit card numbers, national ID numbers, BPJS numbers, medical records, or email addresses. Regulations like GDPR, PCI-DSS, and Indonesia's PDP Act require sensitive data to be encrypted at rest. But here's the classic problem: where to store the encryption keys? If keys are stored in application code, in environment variables, or in config files — that's the same as putting the padlock key right next to the padlock.

Vault Transit radically changes this pattern: applications never see the encryption key at all. The application only sends data to Vault, Vault holds the key, then returns the result. Let's break down why this matters so much and how it works.

Main Discussion

The Traditional Application Encryption Problem

Technically, anyone can encrypt data in their applications — for example with an AES library in Node.js, Python, or Go. But the problem isn't the ability to encrypt, it's key management. When every application team encrypts on their own, here's what happens:

  1. Key sprawl — every application has its own keys, stored in its own way, in environment variables or even source code. There's no single place tracking "who holds which key."
  2. Rotation difficulty — when keys need rotating (required periodically, or in an emergency leak), applications must be modified and redeployed. With dozens of apps, this takes weeks.
  3. Inconsistent algorithms — one team uses AES-256-GCM, another uses AES-CBC with a wrong IV, a third uses MD5 for hashing. Security audits become a nightmare.
  4. Keys scattered across many developers' hands — the more people "carrying" keys, the bigger the attack surface.
AspectIn-App EncryptionEncryption-as-a-Service
Key locationIn app code / env varsCentralized in Vault, apps never see it
Key rotationRedeploy the appOne API command, without changing the app
Algorithm consistencyDepends on each teamStandard, centrally managed
Encryption usage auditNoneComplete via Vault audit logs
Secret knowledge exposure to developersHighZero — developers only need the transit path
Implementation complexityRequires cryptography expertiseOne HTTP call

Important

The core principle behind EaaS: separate keys from data. Applications may, and indeed should, store encrypted data, but the encryption keys must live in a separate, centralized, controlled, and audited place. This is exactly the same philosophy as cloud KMS (Key Management Service) — but with the freedom to run on-premise and multi-cloud.

The Transit Concept: Encryption as an API

In short, the Transit workflow is:

  1. The application sends plaintext (original data) to Vault: vault write transit/encrypt/my-app-key plaintext=$(echo "..." | base64).
  2. Vault encrypts it with the key stored inside Vault, returning ciphertext (encrypted data) to the application.
  3. The application stores the ciphertext in its database.
  4. When the data is needed again, the application sends the ciphertext back: vault write transit/decrypt/my-app-key ciphertext=....
  5. Vault decrypts it and returns the plaintext.

The key point that's often misunderstood: Vault never stores the data. Ciphertext lives in the application's database; Vault is just an encryption machine holding the keys. If you delete the data in the application's database, it's gone from the entire system — Vault has no copy.

Enable the Transit Secrets Engine
vault secrets enable transit

Output:

vault secrets enable transit output
Success! Enabled the transit secrets engine at: transit/

Creating an Encryption Key

Every use of Transit starts with a key. The key name is the only "address" the application needs to know:

Create an encryption key
vault write -f transit/keys/my-app-key

Output:

vault write -f transit/keys output
Key                       Value
---                       -----
allow_plaintext_backup    false
auto_rotate_period        0s
deletion_allowed          false
derived                   false
exportable                false
keys                      map[1:map[creation_time:2026-08-02T09:00:00Z name:my-app-key version:1]]
latest_version            1
min_available_version     0
min_decryption_version    1
supports_decryption       true
supports_derivation       true
supports_encryption       true
supports_signing          true
type                      aes256-gcm96

Notice a few important things:

  • latest_version: 1 — keys have a versioning system like KV. This version is crucial for rotation.
  • type: aes256-gcm96 — the default key type, AES-256 with GCM (authenticated encryption). A very strong 256-bit key.
  • supports_decryption: true — this key can be used for both encryption and decryption.

Note

The -f flag in vault write -f transit/keys/my-app-key means "don't ask for any values" — because creating a key doesn't require extra arguments. This pattern is also used for rotation operations later.

Encryption: Sending Data to Vault

Transit accepts plaintext input in base64 format. Why base64? Because encryption works on bytes, and base64 is the standard way to represent bytes (including binary or unicode character data) as text safe to send over an API.

Encrypt sensitive data
vault write transit/encrypt/my-app-key plaintext=$(echo -n "NIK: 3273121234567890" | base64)

Output:

vault write transit/encrypt output
Key           Value
---           -----
ciphertext    vault:v1:k2x9mQ8pLzV7nB4cX1dF6gH3jR5sT0wY...

The resulting ciphertext has the format vault:v1:.... The vault: part marks the ciphertext's origin, and v1 is the key version used for encryption. This format matters because when the key is rotated, Vault can still decrypt old data encrypted with a previous key version.

Decryption: Returning the Data

Decrypt ciphertext
vault write transit/decrypt/my-app-key ciphertext="vault:v1:k2x9mQ8pLzV7nB4cX1dF6gH3jR5sT0wY..."

Output:

vault write transit/decrypt output
Key       Value
---       -----
plaintext TklLOiAzMjczMTIxMjM0NTY3ODkw

To see the original data, decode the base64 result:

Decode the plaintext result
echo -n "TklLOiAzMjczMTIxMjM0NTY3ODkw" | base64 -d

Output:

base64 -d output
NIK: 3273121234567890

Let's summarize the entire encrypt-decrypt flow in one code-group for easy comparison:

vault write transit/encrypt/my-app-key \
    plaintext=$(echo -n "NIK: 3273121234567890" | base64)

Key Rotation: The Secret to Long-Term Security

This is Transit's main strength over in-app encryption: zero-downtime key rotation. When you rotate a key:

Rotate the key
vault write -f transit/keys/my-app-key/rotate

Output:

Key rotation output
Success! Data written to: transit/keys/my-app-key/rotate

What happens behind the scenes:

  1. The key version advances to 2 (check with vault read transit/keys/my-app-key).
  2. All new encryptions automatically use version 2.
  3. All old data encrypted with version 1 can still be decrypted — because Vault keeps old versions as decryption keys.
  4. The application doesn't need to change at all. Zero downtime, zero deploys.
View key status after rotation
vault read transit/keys/my-app-key

Output:

vault read transit/keys output
Key                       Value
---                       -----
latest_version            2
min_available_version     0
min_decryption_version    1
keys                      map[1:map[creation_time:2026-08-02T09:00:00Z name:my-app-key version:1] 2:map[creation_time:2026-08-02T10:00:00Z name:my-app-key version:2]]

Notice that keys now contains both version 1 and 2. Version 1 isn't deleted — it's kept for decrypting old data. This is what's called key versioning.

To list all keys available on the transit mount:

List all keys on the transit mount
vault list transit/keys

Output:

vault list transit/keys output
Keys
----
my-app-key

Integration Pattern: Encrypt on Write, Decrypt on Read

Now that we understand the primitives, let's see how this pattern is used in real applications. The most common flow is called encrypt-at-write, decrypt-at-read: when the application stores PII in the database, the data is first encrypted via Vault; when the data is read back for display, the application decrypts it via Vault. The database only stores ciphertext.

vault write transit/encrypt/my-app-key \
    plaintext=$(echo -n "arman@devvnull.local" | base64)

From the application's side, the logic is simple: call the Vault API twice — once when storing, once when displaying. The remarkable part: the application never knows its encryption key, and sensitive data is never stored in its original form in the database. If the database leaks, what leaks is only ciphertext — useless without Vault.

Re-Wrap: Refreshing Old Data

After rotation, old ciphertext is still encrypted with version 1. For optimal security, old data should be re-encrypted to the new version — removing dependence on the old key. This process is called re-wrapping:

Re-wrap ciphertext to the latest version
vault write transit/rewrap/my-app-key \
    ciphertext="vault:v1:k2x9mQ8pLzV7nB4cX1dF6gH3jR5sT0wY..."

Output:

vault write transit/rewrap output
Key           Value
---           -----
ciphertext    vault:v2:rT4uW0xYqN9sL7bVc2eF5gH8jK1mP0nZ...

Notice how the v1 in the input ciphertext becomes v2 in the output. The same data, but now encrypted with key version 2 — without ever exposing the plaintext to the application. This is why rewrap is safer than decrypt-then-encrypt: the data is never "opened" inside the application.

Tip

For keys with routine rotation (for example, a 90-day rotation policy), combine auto_rotate_period when creating the key — e.g. vault write transit/keys/app period="8760h" for automatic yearly rotation. Vault will rotate the key periodically without manual intervention, and thanks to versioning, all old ciphertext remains decryptable.

Real-World Use Cases in Indonesia

To ground this, here are scenarios where Transit is used in the real world:

ScenarioData EncryptedWhy Transit Fits
E-commerce / Payment gatewayCredit card numbers, CVV (with tokenization)Meets PCI-DSS, key rotation without downtime
Fintech & BankingNational ID numbers, account numbers, transaction balancesPDP Act, audit trail of encryption usage
Hospitals / Health-techMedical records, lab results, patient dataHealth data is highly sensitive, at-rest encryption required
Multi-tenant SaaSCustomer API keys, PIIPer-tenant encryption with different keys (derived + context)
E-commerce appsAddresses, customer phone numbersCompliance & trust

Warning

It's important to understand Transit's boundaries: Transit doesn't store data and doesn't replace the database or storage function. It's just an encryption machine. For large volumes of data, send it to Transit in batches; for needs requiring centralized secret storage, use KV. Don't confuse "encrypting data" (Transit) with "storing secrets" (KV) — they're often thought of as the same, yet their architectural roles differ.

Common Transit Mistakes

MistakeSymptomSolution
Not encoding plaintext to base64Error: plaintext is required / corrupted dataAlways `echo -n "..."
Storing plaintext in Vault "to be safe"Data ends up scattered in many placesCiphertext in the app database, plaintext not stored
Forgetting that old ciphertext remains valid after rotationPanic when decrypting old dataThat's a feature! Old versions are kept for decryption
Assuming rewrap = decryptTrying to decrypt first, then encrypt againUse rewrap so plaintext never leaves
Using the same key for all tenantsOne app leak = all data readableConsider derived keys + per-app context
Never rotating keysRisk if one key leaksSchedule routine rotation (manual / auto_rotate_period)

Conclusion

In episode 6, we've covered the Transit Secrets Engine (Encryption-as-a-Service): why applications shouldn't manage their own encryption keys, how Transit works as an encryption machine via API, the full create key → encrypt → decrypt flow, and the key rotation and re-wrap mechanisms that allow key changes without downtime and without exposing plaintext.

The essence of this episode: Vault Transit separates keys from data — the application manages data, Vault manages keys. With this, compliance with regulations like PCI-DSS and the PDP Act becomes far easier to meet, because at-rest encryption is done by keys that are centralized, audited, and easy to rotate.

In episode 7, we'll cover the secrets engine that protects the communication layer: the PKI Secrets Engine — how Vault acts as an automatic Certificate Authority, issuing short-lived TLS certificates for internal services, and solving the classic problem of certificates expiring without anyone knowing. Keep your enthusiasm up!

Learn Vault - Transit Secrets Engine (Encryption-as-a-Service / EaaS) | Learn Secret Management with HashiCorp Vault