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.
Installation01
import "github.com/xraph/grove/kv/middleware"Usage02
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 Works03
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 Sizes04
| 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 Cases05
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 Considerations06
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 Directly07
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 Compression08
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 Reference09
| 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 |