At-Rest Encryption
SochDB can encrypt data on disk with AES-256-GCM-SIV, a
nonce-misuse-resistant authenticated encryption (AEAD) cipher. The encryption
layer lives in the storage engine (sochdb-storage) and is designed to protect
data blocks, the write-ahead log (WAL), and checkpoint files.
This page targets the core engine 2.0.3 storage layer
(sochdb-storage::encryption). The language SDKs version independently
(Python 0.5.9, Node.js 0.5.3, Go 0.4.5).
At-rest encryption is currently a library capability, not a runtime feature
of the sochdb-grpc-server binary. The EncryptionEngine type exists and is
unit-tested, but there is no CLI flag to enable at-rest encryption, and the
server's main.rs does not construct or install an EncryptionEngine. Treat
everything below as the available API plus planned server wiring — not as a
flag you flip in production today. The key-loading plumbing
(encryption-key secret / SOCHDB_ENCRYPTION_KEY) is in place; the final
hookup into the server data path is pending.
What gets encrypted
The encryption module is intended to cover the on-disk surfaces of the storage engine:
- Data blocks — persisted with per-block random nonces.
- WAL entries — encrypted via an in-place append path
(
encrypt_in_place). - Checkpoint files — encrypted at write time.
A KEK/DEK (key-encryption-key / data-encryption-key) wrapping scheme is described conceptually in the module, but the unit of configuration you provide is a single 32-byte key (see below).
The cipher: AES-256-GCM-SIV
| Property | Value |
|---|---|
| Algorithm | AES-256-GCM-SIV (AEAD) |
| Nonce-misuse resistant | Yes — safe even if a nonce repeats |
| Key length | Exactly 32 bytes (256-bit) |
| Nonce length | 12 bytes (random per encryption) |
| Auth tag length | 16 bytes |
AES-256-GCM-SIV is chosen specifically because it is nonce-misuse resistant: accidental nonce reuse degrades gracefully rather than catastrophically leaking the key or plaintext, which matters for a high-throughput WAL and block store that may generate many nonces.
Wire format
Every encrypted payload is self-describing. The format is a 1-byte version, a 12-byte random nonce, then the ciphertext with its appended 16-byte tag:
[1 byte version = 1][12-byte random nonce][ciphertext + 16-byte auth tag]
On decryption, the engine validates the version byte and the authentication tag before returning plaintext; a tampered or truncated payload fails to decrypt.
Keys
The engine takes exactly 32 bytes of key material:
EncryptionEngine::new(&[u8; 32]).
Supplying a key
Two sources are recognized by the server's secrets plumbing:
| Source | Format | Notes |
|---|---|---|
Secret file encryption-key | base64-encoded, decodes to 32 bytes | From a Kubernetes Secrets mount (--secrets-path) |
Env var SOCHDB_ENCRYPTION_KEY | base64-encoded, decodes to 32 bytes | Overrides / supplies the key without a mount |
Both are base64-decoded to 32 bytes by the SecretsProvider::encryption_key()
helper. For Kubernetes deployments, the same --secrets-path mount that carries
jwt-secret, api-keys, and the TLS material can also carry encryption-key.
Generating a key
The library can generate a random 32-byte key (EncryptionEngine::generate_key()).
You can also produce one with standard tooling and base64-encode it for the
secret / env var:
# 32 random bytes, base64-encoded — suitable for SOCHDB_ENCRYPTION_KEY
openssl rand -base64 32
export SOCHDB_ENCRYPTION_KEY="$(openssl rand -base64 32)"
Key zeroization
The in-memory key (EncryptionKey) is zeroized on drop (via the zeroize
crate), so the 32-byte secret is wiped from memory when the engine is torn down
rather than lingering in freed heap.
Using the EncryptionEngine (library API)
The engine is a Rust API in sochdb-storage. Construct it from a 32-byte key,
then encrypt and decrypt opaque byte buffers.
use sochdb_storage::encryption::EncryptionEngine;
// 1. Obtain a 32-byte key. In production this comes from the
// `encryption-key` secret or SOCHDB_ENCRYPTION_KEY (base64-decoded).
let key = EncryptionEngine::generate_key(); // [u8; 32]
// 2. Build the engine.
let engine = EncryptionEngine::new(&key);
// 3. Encrypt: output is [version][12B nonce][ciphertext + 16B tag].
let plaintext = b"sensitive row data";
let ciphertext = engine.encrypt(plaintext)?;
// 4. Decrypt: validates the version byte and the auth tag.
let recovered = engine.decrypt(&ciphertext)?;
assert_eq!(recovered, plaintext);
Because the cipher is nonce-misuse resistant, the engine generates a fresh random 12-byte nonce per call and prepends it to the output, so callers never manage nonces themselves.
For the write-ahead log, the engine exposes an in-place variant
(encrypt_in_place) so the append-only WAL writer can encrypt a buffer it
already owns without an extra allocation. This is part of the same library API.
Planned server wiring
When the server hookup lands, the intended operational model is:
- Mount or export the 32-byte key as the
encryption-keysecret (base64) orSOCHDB_ENCRYPTION_KEY. - The server's
SecretsProviderdecodes it to 32 bytes and constructs anEncryptionEngine. - The storage engine transparently encrypts data blocks, WAL entries, and checkpoints on write and decrypts on read.
Until that wiring is present in sochdb-grpc-server, supplying
SOCHDB_ENCRYPTION_KEY alone does not encrypt the server's on-disk state —
the key is loadable, but no EncryptionEngine is installed in the data path.
If your threat model requires at-rest encryption today, treat it as not yet available at the server level and layer encryption underneath SochDB — for example, an encrypted block device / volume (LUKS, dm-crypt) or a cloud-provider encrypted disk. Revisit this guide when the server CLI exposes the toggle.
Encryption in transit (related)
At-rest encryption is distinct from transport security. For encrypting the gRPC
connection itself, the server does support TLS and mTLS today via
--tls-cert, --tls-key, and --tls-ca. See the
Deployment guide for TLS configuration and the
Security guide for authentication, RBAC, and secrets.
Summary
| Aspect | Status |
|---|---|
| Cipher (AES-256-GCM-SIV) | Implemented, unit-tested |
| 32-byte key, base64 secret / env loading | Implemented (SecretsProvider) |
Wire format [version][nonce][ct+tag] | Implemented |
| Key zeroization on drop | Implemented |
EncryptionEngine library API | Available |
Server CLI flag / main.rs data-path wiring | Not yet wired |
| TLS / mTLS in transit | Available today (see Deployment) |