Grove
Middleware

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)
ParameterDescription
keyAES 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 bytes

Read Path (Decrypt)

GET "user:123"
  |
  v
Driver returns [nonce || ciphertext]
  |
  v
[Split nonce from ciphertext] --> [AES-GCM Open(nonce, ciphertext)] --> plaintext
  |
  v
Return decrypted value

The 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 LengthAlgorithmSecurity Level
16 bytesAES-128Good for most use cases
24 bytesAES-192Higher security margin
32 bytesAES-256Recommended 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

MethodDescription
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

On this page