Encryption Middleware
Transparent AES-GCM encryption for KV values at rest.
The encryption middleware transparently encrypts all values before they are stored and decrypts them when they are read. It uses AES-GCM (Galois/Counter Mode), which provides both confidentiality and integrity for stored data.
Installation
import "github.com/xraph/grove/kv/middleware"Usage
import (
"crypto/rand"
"github.com/xraph/grove/kv"
"github.com/xraph/grove/kv/middleware"
)
// Generate a 32-byte key for AES-256
key := make([]byte, 32)
if _, err := rand.Read(key); err != nil {
log.Fatal(err)
}
encryptHook, err := middleware.NewEncrypt(key)
if err != nil {
log.Fatal(err)
}
store, err := kv.Open(drv,
kv.WithHook(encryptHook),
)Constructor
func NewEncrypt(key []byte) (*EncryptHook, error)| Parameter | Description |
|---|---|
key | AES key -- must be 16 bytes (AES-128), 24 bytes (AES-192), or 32 bytes (AES-256) |
The constructor returns an error if the key length is invalid.
How It Works
The encryption middleware uses AES-GCM, an authenticated encryption mode that provides:
- Confidentiality -- values are encrypted and unreadable without the key
- Integrity -- any tampering with the ciphertext is detected on decryption
- Unique nonce per value -- a cryptographically random nonce is generated for each encryption operation, ensuring that encrypting the same value twice produces different ciphertext
Write Path (Encrypt)
SET "user:123" = {"name": "Alice", "ssn": "123-45-6789"}
|
v
[Generate random nonce] --> [AES-GCM Seal(nonce, plaintext)] --> [nonce || ciphertext]
|
v
Driver stores encrypted bytesRead Path (Decrypt)
GET "user:123"
|
v
Driver returns [nonce || ciphertext]
|
v
[Split nonce from ciphertext] --> [AES-GCM Open(nonce, ciphertext)] --> plaintext
|
v
Return decrypted valueThe nonce is prepended to the ciphertext during encryption and stripped during decryption. This is handled automatically -- the application sees only plaintext values.
Key Sizes
| Key Length | Algorithm | Security Level |
|---|---|---|
| 16 bytes | AES-128 | Good for most use cases |
| 24 bytes | AES-192 | Higher security margin |
| 32 bytes | AES-256 | Recommended for sensitive data |
AES-256 (32-byte key) is recommended for production use.
Use Cases
- Sensitive data at rest -- encrypt PII, credentials, tokens, or financial data stored in the KV store
- Compliance requirements -- meet encryption-at-rest requirements for regulations like GDPR, HIPAA, or PCI-DSS
- Shared infrastructure -- protect data when the backing store is shared or managed by a third party
Key Management Considerations
The encryption middleware requires you to provide the key. How you manage that key is critical to the security of your data:
- Do not hard-code keys in source code or configuration files checked into version control.
- Use a secrets manager such as AWS Secrets Manager, HashiCorp Vault, or GCP Secret Manager to store and rotate keys.
- Key rotation: The current middleware does not support automatic key rotation. To rotate keys, you must re-encrypt existing data with the new key. A common approach is to read all values, decrypt with the old key, and re-encrypt with the new key during a migration.
- Key loss: If you lose the encryption key, all encrypted data becomes permanently unrecoverable. Ensure your key management system has appropriate backup and recovery procedures.
Encrypt and Decrypt Directly
The EncryptHook exposes Encrypt and Decrypt methods for direct use outside the middleware pipeline:
encryptHook, err := middleware.NewEncrypt(key)
if err != nil {
log.Fatal(err)
}
// Encrypt
ciphertext, err := encryptHook.Encrypt([]byte("sensitive data"))
// Decrypt
plaintext, err := encryptHook.Decrypt(ciphertext)Combining with Compression
When using both compression and encryption, register the compression hook before the encryption hook. Encrypted data has high entropy and does not compress effectively:
store, err := kv.Open(drv,
kv.WithHook(middleware.NewCompress(middleware.Gzip)),
kv.WithHook(encryptHook),
)API Reference
| Method | Description |
|---|---|
NewEncrypt(key) | Create a new encryption hook (returns error if key length is invalid) |
Encrypt(plaintext) | Encrypt a byte slice, returning nonce || ciphertext |
Decrypt(ciphertext) | Decrypt a byte slice, stripping the prepended nonce |