Key Management and Recovery
The Reference Implementation encrypts service instance configurations and credential decryption keys at rest under DATA_ENCRYPTION_KEY. This page is the operational contract for that key: how to generate and hold it, how backups pair with it, and how recovery works. The Encryption Audit and Encryption Key Rotation pages own the two procedures this contract relies on.
The key and what it protects
Generate the key with openssl rand -hex 32, producing the 64-character hexadecimal string the application requires. The placeholder value published in .env.example is for local development only; startup validation enforces this, and that section defines exactly when a deployment counts as local development.
Uniqueness and storage
Each environment has one unique key, held in the deployment's secret store and never committed to version control. Every process and replica in an environment shares that one key; replicas configured with different values are a fault, not separate environments. The serving application resolves and caches exactly one active key. SERVICE_ENCRYPTION_KEY is a deprecated alias for the same value, not a second key, and startup fails when the two names disagree. The one exception is the offline rotation command, which deliberately reads a second variable while the application is stopped.
Keys do not travel between environments. Supplying a key other than a backup's paired one fails the audit exactly as key loss would (for any backup holding at least one encrypted envelope), and reusing one environment's key in another means new data is silently written under a key that does not belong there.
Backups pair with the key
A database backup without its paired key is not a recovery artefact. Stored envelopes carry no key identifier, and the serving application holds exactly one active key, so losing the key makes every stored envelope permanently unreadable: every service instance configuration, and every credential decryption key that has been wrapped. Credential rows created before v0.4 keep their stored keys in plaintext until the backfill wraps them, so they, like credentials stored unencrypted, do not depend on the key and survive its loss.
For every backup, record which key opens it: the backup's identity (timestamp or version), its source environment, the secret-store version of the paired key, and, when a backup sits on one side of a rotation, which side. Also record the audit's suspect and corrupted row ids for that backup, so a later recovery can tell a pre-existing finding apart from new corruption. Record the secret-store reference, never the raw key, and keep raw key material out of logs and tickets.
Retire a key only when every retained backup taken under it has itself been retired. A completed rotation moves the live database to the new key; every retained backup still opens only under whichever key was active when it was taken, which after several rotations spans several key generations. A retired-too-early key turns those backups into dead weight even though the rotation succeeded. A recorded key is recoverable when its secret-store version can actually be retrieved; verify that periodically by retrieving it, rather than by decrypting something from every backup.