Vault Transit Engine: Encryption as a Service Without the Key Management Headache
Vault's Transit engine moves encryption keys out of your application code. Here's how it works, where it shines (key rotation without rewrites, convergent encryption for lookups), and the operational trade-offs—latency, key loss risk, and data residency—that decide whether it belongs in your stack.
18 Jul 2025, 00:00 UTC

The Problem: Encryption Without Key Custody
Your application needs to encrypt PII before writing to a database. You could pull in a crypto library, generate keys, store them in a config file, and hope rotation never becomes urgent. Or you could push the cryptographic operations to a service that already handles key lifecycle, audit logging, and HSM-backed storage. HashiCorp Vault's Transit Secrets Engine does exactly that—it offers encryption as a service (EaaS) so your code never sees a raw key.
How Transit Changes the Trust Boundary
Transit moves key material out of application memory entirely. The app sends plaintext to Vault via the /transit/encrypt/<key> endpoint and receives a ciphertext blob prefixed with vault:v1: (the version indicates the key generation used). Decryption reverses the flow. The key never leaves Vault's barrier storage; even if an attacker compromises the application host, they only get ciphertext and API tokens with limited TTL.
This model shifts the trust boundary from "application + config file" to "Vault cluster + network path." You still need to secure the token used to call Transit, but that token can be short-lived, bound to a specific policy, and revoked instantly.
Key Rotation Without Re-encrypting Data
One of Transit's strongest operational features is versioned keys. When you rotate a key (vault write -f transit/keys/my-key/rotate), Vault increments the version counter. New encryptions use the latest version; existing ciphertexts still decrypt because Vault retains all previous versions. You can also set min_decryption_version to enforce a cutoff—useful for compliance windows where old key versions must be retired.
Rotation is a metadata operation, not a data rewrite. For a table with millions of encrypted rows, this avoids the "re-encrypt everything" maintenance window that plagues client-side key management.
Convergent Encryption: Deterministic Ciphertext for Lookups
Standard encryption is probabilistic—same plaintext yields different ciphertext each time. That breaks database indexes and WHERE clauses. Transit supports convergent encryption (enabled at key creation with convergent_encryption=true and a derived flag), which derives the IV from the plaintext itself. The result: identical plaintext produces identical ciphertext for a given key version, enabling encrypted-column lookups.
The trade-off is semantic: an observer with access to ciphertexts can detect duplicate values. For low-entropy fields (boolean flags, enums), this leaks information. Use convergent encryption only on high-entropy data (emails, IDs, tokens) and never on passwords—those belong in a dedicated KDF like Argon2, not reversible encryption.
Worked Example: Enable, Create, Encrypt, Decrypt
Run these against a Vault dev server (vault server -dev) or a production cluster with appropriate permissions. You need write on transit/keys/* and update on transit/encrypt/* and transit/decrypt/*.
# 1. Enable the Transit secrets engine (once per cluster)
vault secrets enable transit
# 2. Create a named key for your service
vault write -f transit/keys/orders-api
# 3. Encrypt a value (base64-encoded plaintext)
echo -n "[contact removed]" | base64
# dXNlckBleGFtcGxlLmNvbQ==
vault write transit/encrypt/orders-api plaintext=dXNlckBleGFtcGxlLmNvbQ==
# Returns: vault:v1:SDF8s7... (ciphertext)
# 4. Decrypt it back
vault write transit/decrypt/orders-api ciphertext=vault:v1:SDF8s7...
# Returns base64 plaintext → decode to verify
In production, use the HTTP API with a client token. The ciphertext is safe to store in Postgres, DynamoDB, or a log file—it contains no key material.
Trade-offs You Cannot Ignore
- Latency and throughput: Every encrypt/decrypt is a network round-trip to Vault. At 10k req/s, you need a performant cluster (or batch via
/transit/batch-encrypt). Benchmark before committing. - Data residency: Transit does not store your ciphertext. The application must persist it. If you need geographic key residency, deploy Vault in each region with separate clusters.
- Key loss = data loss: Deleting a Transit key (
vault delete transit/keys/orders-api) makes every ciphertext encrypted with it permanently unreadable. Treat key deletion like dropping a database—require multi-party approval and backup the key viavault read transit/backup/orders-api. - No search on non-convergent fields: Without convergent encryption, you cannot index encrypted columns. Plan your schema accordingly.
When to Adopt Transit
Use Transit when:
- You already run Vault for secrets and want a consistent audit trail for crypto operations.
- Key rotation must happen without application downtime or data migration.
- Your threat model includes application-server compromise but not Vault compromise.
Stick with client-side encryption (libsodium, AWS Encryption SDK, Google Tink) when:
-
li>Sub-millisecond latency is non-negotiable and you can manage key rotation yourself.
- You need offline encryption (mobile, edge devices) without Vault connectivity.
- Regulatory requirements mandate keys never leave a specific HSM you control directly.
Next Step: Verify in Your Environment
Spin up a dev Vault, run the example above, then measure encrypt/decrypt latency under your expected concurrency. Check the vault:v1: prefix in ciphertexts—it confirms the versioning scheme. If latency fits your SLA and your team can operate Vault reliably, Transit removes an entire class of key-management bugs from your codebase.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.