Keys and secrets
| Secret | Purpose |
|---|---|
encryption.key |
Master key, at least 32 bytes. Anvil derives a key per purpose from it (HKDF, Anvil\Security\KeyRing): component tokens, the tenant switch, encrypted TOTP secrets and recovery codes, API signing secrets and list cursors, and the audit key unless anvil.auditKey is set. |
encryption.previousKeys |
Master keys used before a rotation, comma separated, oldest first. They only decrypt data of the old key. |
anvil.auditKey, anvil.auditPreviousKeys |
Key of the audit log and the keys used before it. |
anvil.sso.<id>.clientSecret, anvil.sso.<id>.spPrivateKey |
Single sign-on client secret and SAML key. |
database.default.password |
Database. |
Generate keys on a trusted machine and print them instead of writing a
.env:
php spark key:generate --show # encryption.keyphp spark anvil:audit:generate-key # anvil.auditKeyKeep them in a secret store and hand them to the server as .env (readable
only by the PHP user and the deploy user) or as environment variables.
CodeIgniter also reads environment variables with _ instead of .
(encryption_key, anvil_auditKey); they must be set for PHP-FPM and
for the user that runs the scheduled commands. Never commit .env, and do
not reuse the keys of development or staging.
Give the audit log a key of its own from the start (anvil.auditKey): it
can then be rotated on its own, as described in
Key rotation.
Rotating encryption.key
Section titled “Rotating encryption.key”Rotate the master key when it may be known to others (e.g. a former
administrator, a leaked .env or backup) and at the intervals of your key
management policy.
-
php spark anvil:audit:verify: the audit log must be intact. -
Generate a new key with
php spark key:generate --show. -
On every server at the same time: append the current key to
encryption.previousKeys(comma separated, oldest first) and setencryption.keyto the new key.encryption.key = hex2bin:…new key…encryption.previousKeys = hex2bin:…key used so far… -
php spark anvil:rekey: encrypts the TOTP secrets of all users and the signing secrets of all API clients (of all tenants) with the new key, recordssecurity.rekeyedin the audit log and reports secrets that no key opens. -
Remove the old key from
encryption.previousKeysonce nothing needs it any more (see below), and from the backups of the configuration.
What happens in between:
- New data is always encrypted with the current key; previous keys only decrypt.
- Logged in users have to reload open pages: component tokens, list cursors of the API and the tenant switch use only the current key, they are short-lived anyway.
- Recovery codes are stored as keyed hashes and cannot be encrypted
again. They keep working while their key is in
encryption.previousKeys;anvil:rekeyreports how many users still have codes of a previous key. Remove the key when these users have generated new codes, or accept that their remaining codes stop working (TOTP itself keeps working). - Audit log: without
anvil.auditKeyits key is derived fromencryption.key, andencryption.previousKeysare previous audit keys as well. The first new entry recordsaudit.key_rotated. Keep these keys as long as the log is kept, inanvil.auditPreviousKeysonce they leaveencryption.previousKeys; better, give the audit log a key of its own.