HashiCorp Vault — Encryption as a Service
Vault Transit is “encryption as a service” — a centralized cryptographic service for your applications.
The Problem
Applications that handle their own encryption face multiple challenges:
- Key management: Where do you store the encryption key?
- Key rotation: How do you rotate keys without downtime?
- Algorithm choice: Is AES-256-GCM the right choice? What about authenticated encryption?
- Implementation bugs: Crypto libraries are easy to misuse
- Audit: Who encrypted what, and when?
Transit Architecture
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Web App │ │ Mobile API │ │ Batch Job │
│ │ │ │ │ │
└──────┬──────┘ └──────┬──────┘ └──────┬──────┘
│ │ │
│ ┌─────────────┴──────────────┐ │
└───▶│ Vault Transit │◀──┘
│ │
│ ┌─────────┐ ┌──────────┐ │
│ │ Key: app │ │ Key: pii │ │
│ │ AES-256 │ │ ChaCha20 │ │
│ └─────────┘ └──────────┘ │
│ │
│ Encryption keys NEVER │
│ leave Vault's barrier │
└────────────────────────────┘
Key Types
| Key Type | Algorithm | Use Case |
|---|---|---|
aes128-gcm96 |
AES-128 with GCM | General encryption |
aes256-gcm96 |
AES-256 with GCM | Default. Strong general encryption |
chacha20-poly1305 |
ChaCha20-Poly1305 | Software-optimized encryption |
ed25519 |
Ed25519 | Signing only |
ecdsa-p256 |
ECDSA P-256 | Signing only |
ecdsa-p384 |
ECDSA P-384 | Signing only |
ecdsa-p521 |
ECDSA P-521 | Signing only |
rsa-2048 |
RSA 2048-bit | Encryption + signing |
rsa-3072 |
RSA 3072-bit | Encryption + signing |
rsa-4096 |
RSA 4096-bit | Encryption + signing |
Create Encryption Keys
# Enable transit
vault secrets enable transit
# Create a key with default settings (aes256-gcm96)
vault write -f transit/keys/my-app-key
# Create a key with specific type
vault write transit/keys/signing-key type=ed25519
# Create a key allowing export (for special cases)
vault write transit/keys/exportable-key type=aes256-gcm96 exportable=true
# Create a convergent encryption key (same plaintext = same ciphertext)
vault write transit/keys/deterministic-key \
type=aes256-gcm96 \
convergent_encryption=true \
derived=true
Key Properties
vault read transit/keys/my-app-key
Expected output:
Key Value
--- -----
allow_plaintext_backup false
auto_rotate_period 0s
deletion_allowed false
derived false
exportable false
keys map[1:1705312800]
latest_version 1
min_available_version 0
min_decryption_version 1
min_encryption_version 0
name my-app-key
supports_decryption true
supports_derivation true
supports_encryption true
supports_signing false
type aes256-gcm96
Transit Operations
| Operation | Endpoint | Description |
|---|---|---|
| Encrypt | transit/encrypt/KEY |
Encrypt plaintext → ciphertext |
| Decrypt | transit/decrypt/KEY |
Decrypt ciphertext → plaintext |
| Rewrap | transit/rewrap/KEY |
Re-encrypt with latest key version |
| Rotate | transit/keys/KEY/rotate |
Create new key version |
| Datakey | transit/datakey/TYPE/KEY |
Generate a data encryption key |
| Sign | transit/sign/KEY |
Create digital signature |
| Verify | transit/verify/KEY |
Verify digital signature |
| HMAC | transit/hmac/KEY |
Generate HMAC |
| Hash | transit/hash |
Hash data (no key needed) |
Convergent Encryption
Convergent encryption produces the same ciphertext for the same plaintext, enabling encrypted search:
# Create a convergent key
vault write transit/keys/search-key \
type=aes256-gcm96 \
convergent_encryption=true \
derived=true
# Encrypt with a context (required for convergent)
vault write transit/encrypt/search-key \
plaintext=$(echo -n "alice@example.com" | base64) \
context=$(echo -n "email-lookup" | base64)
# Always returns the same ciphertext for "alice@example.com" with "email-lookup" context
Quick Reference
| Command | Description |
|---|---|
vault secrets enable transit |
Enable Transit |
vault write -f transit/keys/NAME |
Create key |
vault write transit/keys/NAME type=TYPE |
Key with type |
vault read transit/keys/NAME |
Read key info |
vault list transit/keys |
List all keys |
Next: Encrypt & Decrypt