Envelope encryption

Every sensitive field that lands in MongoDB is sealed with AES-256-GCM. A fresh random data encryption key (DEK) is generated per field and wrapped by the active key encryption key (KEK). This page explains the model, what is encrypted, and what appears on disk.

01The envelope

Every encrypted field in the database carries this shape:

{
  "wrapped_dek":   "<opaque bytes>",   // DEK sealed by the KEK
  "nonce":         "<12–16 bytes>",    // GCM nonce used to encrypt the payload
  "ciphertext":    "<opaque bytes>",   // AES-256-GCM(payload, DEK, nonce)
  "kek_version":   2,                   // which KEK sealed this envelope
  "provider":      "env"                // "env" or "pkcs11"
}

The DEK is never reused across fields — each record gets its own. The KEK, by contrast, is long-lived and versioned so it can be rotated without re-encrypting every record at once (see KEK rotation).

02What is encrypted

Every field you would rather not see in a mongodump is sealed:

CollectionEncrypted fieldWhat it protects
certificate_private_keysencrypted_keyThe private key PEM that pairs with every issued certificate.
acme_accountsencrypted_private_keyThe account-identity key used to sign ACME requests.
msca_connectionsencrypted_credentialsAD service-account username + password.
dns_credentialsencrypted_api_keyEvery DNS provider's API material.
module_credentialsencrypted_valueSSH keys, kubeconfigs, F5/NetScaler/IIS passwords, Vault tokens, Huawei AK/SK.
notification_channelsencrypted_configSMTP passwords, Slack webhooks, Teams webhooks.
TOTP seeds, license blob(inline)2FA shared secrets and license-key material.

PEM certificate bodies (public) and the ACME registration URL are not encrypted — they are safe to expose.

03What is not envelope-encrypted

  • User passwords — bcrypt hashed (not encrypted; irreversible by design).
  • Refresh tokens — SHA-256 hashed (reuse detection requires a fixed digest).
  • API keys — SHA-256 + pepper hashed. The pepper is env-only; see API keys.
  • MongoDB-at-rest — orthogonal concern. If your policy requires at-rest volume encryption, configure it on the MongoDB side (LUKS, cloud-provider CMK, WiredTiger encryption). Envelope encryption protects against a logical leak (mongodump, read-replica snapshot); volume encryption protects against physical theft. Run both.

04KEK providers

The wrap/unwrap of the DEK is delegated to a provider. CertAutoPilot ships with two:

  • env — Phase 1 default. Raw KEK bytes live in CERTAUTOPILOT_ENCRYPTION_ENV_KEK_V{N} environment variables. Simplest to operate; the backend process must be trusted with the key material.
  • pkcs11 — Phase 2. The KEK lives inside a PKCS#11 HSM. CertAutoPilot only holds opaque handles and the wrapped DEK blobs; the key material never leaves the HSM.

The choice is install-locked in MongoDB (kek_install singleton). Swapping between env and pkcs11 on an already-provisioned database is rejected — see provider migration.

05The kek_version field

Every envelope carries the version of the KEK that sealed it. This is what makes rotation incremental:

  1. Before rotation, every envelope has kek_version: 1.
  2. Operator adds V2 key material and restarts each node so the backend loads both versions. V1 is still the keystore's active version — no rotate has happened yet.
  3. Operator runs certautopilot kek rotate --from-version=1 --to-version=2. The worker reads every envelope, unwraps with V1, re-wraps explicitly with V2 (not with the process's own current — this keeps the outer doc.kek_version consistent with the inner envelope kv tag at all times), writes back atomically per record, and flips V2 active in the keystore via SwapActive.
  4. Every live node's heartbeat tick (~30 s) detects the SwapActive and hot-reloads V2 as the current KEK — no rolling restart required. New writes from then on seal with V2. V1 stays loaded for unwrap of any older envelopes (none, if the rotation ran to completion) until kek remove + env-var cleanup clears it.

See KEK rotation for the full runbook.

06The provider field

Tagging each envelope with the provider that sealed it is a safety net: the backend refuses to unwrap a provider: env envelope when the active provider is pkcs11, and vice versa, preventing silent data corruption if someone edits the kek_install record by hand.

07Guarantees and limits

  • Confidentiality + integrity via AES-256-GCM. Tampering with the ciphertext yields an authenticated-decryption failure.
  • Per-field DEK. A leak of one envelope does not weaken any other.
  • In-memory exposure: the backend holds the KEK (for env) or the HSM handle (for pkcs11) in memory; a running-process memory dump reveals it. This is inherent to online cryptosystems — use OS-level memory protection (disable core dumps for the unit, enable ProtectKernelTunables, and so on).
  • Key custody is on you. CertAutoPilot does not have a "recover my KEK" flow. Lose the KEK → envelopes become unreadable. Back up secrets (standalone secrets.env or the K8s Secret) alongside MongoDB.