Skip to main content

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.

Versions

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).

Library API, not yet a server toggle

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

PropertyValue
AlgorithmAES-256-GCM-SIV (AEAD)
Nonce-misuse resistantYes — safe even if a nonce repeats
Key lengthExactly 32 bytes (256-bit)
Nonce length12 bytes (random per encryption)
Auth tag length16 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:

SourceFormatNotes
Secret file encryption-keybase64-encoded, decodes to 32 bytesFrom a Kubernetes Secrets mount (--secrets-path)
Env var SOCHDB_ENCRYPTION_KEYbase64-encoded, decodes to 32 bytesOverrides / 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.

WAL append path

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:

  1. Mount or export the 32-byte key as the encryption-key secret (base64) or SOCHDB_ENCRYPTION_KEY.
  2. The server's SecretsProvider decodes it to 32 bytes and constructs an EncryptionEngine.
  3. 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.

Do not assume data is encrypted at rest yet

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.


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

AspectStatus
Cipher (AES-256-GCM-SIV)Implemented, unit-tested
32-byte key, base64 secret / env loadingImplemented (SecretsProvider)
Wire format [version][nonce][ct+tag]Implemented
Key zeroization on dropImplemented
EncryptionEngine library APIAvailable
Server CLI flag / main.rs data-path wiringNot yet wired
TLS / mTLS in transitAvailable today (see Deployment)