posts/0016.md · 2026-04-27
encryption at rest — AES-256-GCM in the storage layer
Pass an `EncryptionKey` to `OxiDb::open_with_options` (or the env var `OXIDB_ENCRYPTION_KEY=/path/to/keyfile`) and every byte that leaves the in-memory representation is encrypted before it hits the disk. AES-256-GCM, per-write random 96-bit nonce, authenticated, no chained mode surprises.
**Where it sits.** The crypto layer wraps `BTreeStorage` and the blob store. `persist()` encrypts the serialized image; `open()` decrypts on load. WAL entries are encrypted individually so a partial write doesn't poison the whole image. Indexes (the BTreeMap structures) live in memory only, get rebuilt from the encrypted store on restart — so an attacker with disk access never sees the structure either.
**Key handling.** The key is a 32-byte file. Mode is 0o600 or we refuse to open. No key derivation step — feed it the raw entropy, mark it as such in your secret manager. KMS integration is a small layer above (encrypt the key file with KMS, decrypt at boot) that we don't ship in the engine.
**Operational.** Encrypted and unencrypted data dirs are wire-incompatible — you can't mix. Backup is `tar czf` of the data dir plus a separate, never-on-disk-together copy of the key. To rotate: re-open with new key flag, persist, swap in. (We're working on a non-blocking rotation; not shipped yet.)
**S3.** `OXIDB_S3_ENCRYPTION_KEY` enables SSE-S3 for the S3-compatible bucket API — same AES-256-GCM, server-managed key. SSE-C also implemented for per-request key clients.