Field encryption
Encrypt some fields before they reach MongoDB, with a key you hold a copy of: what that protects and what it doesn't, how keys work, and where each part is documented.
Some fields (a tax id, a health note, a private comment) must not be readable from a database dump, a backup or an export. Field encryption has backd encrypt them before they reach MongoDB and decrypt them for the callers your rules allow. The key is yours: backd generates it, shows it once, and you keep the only copy outside backd.
# collection.yaml
fields:
health_notes:
encrypt: { key: HEALTH }
reveal: user != nil && hasRole(user, 'doctor')
Review status
The feature has had an internal security review (see the security model). The storage format is published, with test vectors, so that it can be reviewed by someone else; an outside cryptography review is still pending. Until it is done, weigh that when the data is regulated.
What it protects, and what it doesn’t
| Protected | Not protected |
|---|---|
| A database dump or a backup (the values are binary, and open only with the field’s key) | A compromised backd process: it holds the unwrapped data keys in memory while it runs |
| Anyone with database access but not the keys: operators, a stolen disk, a leaked snapshot | Someone who holds the secrets master key and the realm’s secrets (the keys are among them) |
| An export (the server never decrypts for one: ciphertext unless you supply the keys) | Function code that prints a decrypted value to its logs |
Callers who fail the field’s reveal rule: they get null, and know it is hidden | The patterns a blind index shows, if you enable search (equal values have equal indexes) |
It is not a replacement for the other layers, and it is not the same as either of them:
- Disk or database encryption at rest protects a stolen disk and nothing that can read the running database. Here the database never holds the value in the clear.
- Bucket encryption protects files, which this feature does not cover (a file’s contents rely on the bucket’s encryption).
How it works, in four lines
- A field key is 32 random bytes that you hold a copy of. It encrypts nothing but other keys.
- Each field has data keys that encrypt its values (AES-256-GCM, each value bound to its realm, collection, field and document, so a value copied elsewhere fails to decrypt). The field key only wraps the data keys.
- That is why rotating a key is instant: replacing a field key wraps a handful of data keys again and rewrites no document, whatever the size of the collection.
- A value is opaque by default. You can opt in to equality search with a blind index, and decide who sees the plain value with a
revealrule.
Where to read next
| You want to… | Read |
|---|---|
| Create, save, rotate and delete keys; mark fields; search; reveal; change existing data | Field keys and encrypted fields |
| Copy a recipe: a tax id with lookup, notes for doctors, encrypting a field that has data | Recipes |
| Understand a startup refusal or an error | Errors and what to do |
The collection.yaml keys | Data model → Encrypted fields |
| Move encrypted data between instances | Export and import → Encrypted fields |
| Build a tool that reads the stored format | Encrypted fields format (with test vectors) |
| Respond to a leaked key | If credentials leak |
| See it work end to end | Tutorial, chapter 25 |
Encrypt a tax id and look people up by it, show health notes only to doctors, turn encryption on for a field that already has data, and seed staging without exposing the keys.
Errors and what to doEvery refusal field encryption can give at startup, in a request or in a command, what it means and what to change.