Skip to content
vaultigen

Operations

Key management

Which keys Vaultigen uses, how they are generated, where to escrow them, and how to rotate them.

Vaultigen uses several independent keys, so that compromising one does not silently compromise the others. None of them is stored in the database.

The keys

Key Created by Stored in Protects
Installation KEK keyring vaultigen:initialize (or vaultigen:kek:generate) storage/app/private/kek.json Wraps every vault’s data key.
Vault keys Automatically, one per vault Database, wrapped by the KEK Entries, revisions and file keys of one vault.
Audit-integrity key vaultigen:initialize installation.json Checkpoints of the audit log.
Installation ID vaultigen:initialize installation.json Bound into almost every cryptographic operation. Not secret, but must never change.
Passkey secret vaultigen:initialize installation.json Reserved for passkey support.
Backup keyring vaultigen:backup-key:generate storage/app/private/backup.json Encrypts backup sets.
APP_KEY php artisan key:generate .env Laravel’s key: cookies and stored authentication data such as TOTP secrets.

All files are created with mode 0600 and owned by the PHP user.

On Docker Compose, setup creates all of them in data/keys/ next to compose.yaml: app.key (the APP_KEY), installation.json, kek.json and backup.json, owned by the container’s application user (UID 10001). See Docker Compose below.

Generating keys

On a new installation:

php artisan key:generate                              # APP_KEY
php artisan vaultigen:initialize                      # installation.json + kek.json
php artisan vaultigen:backup-key:generate --if-missing  # backup.json

Each command refuses to overwrite an existing key. Never generate new keys for an installation that already holds encrypted data. Restore the escrowed files instead.

Escrow

Losing the KEK keyring makes every vault permanently unreadable. There is deliberately no bypass; a bypass would be a back door.

  • Copy installation.json, kek.json, backup.json and APP_KEY to an escrow immediately after creating them.
  • Keep the backup keyring separate from the KEK keyring and from the backup sets.
  • Make sure a second person can retrieve the escrow if you are unavailable.
  • Update the escrow after every rotation.

System → Operations → Keys shows the identifier and presence of each recovery input, never the values.

Docker Compose

  • Generating: docker compose run --rm setup creates only missing keys, and only for an empty database. It refuses an existing database whose keys are missing; restore your key copy instead. A normal up never creates keys.

  • Escrow: copy the whole folder off the server after setup and after every rotation:

    umask 077
    sudo tar -czf vaultigen-keys.tar.gz -C ~/vaultigen/data keys
    sudo chown "$USER" vaultigen-keys.tar.gz

    Store it with a copy of .env, encrypted and separately from the backups, then delete the local archive. Keep older copies for as long as retained backups need their keys.

  • Rotation commands run inside the web container, for example docker compose exec web php artisan vaultigen:kek:add. The browser actions work as on any installation.

  • Restoring: unpack the archive into data/ (tar -xzf vaultigen-keys.tar.gz -C ~/vaultigen/data) before running the restore.

Environment variables instead of files

Manual installations on hosts that cannot mount secret files may supply the values through the environment:

Variable Replaces
VAULTIGEN_INSTALLATION_ID Installation ID
VAULTIGEN_AUDIT_KEY Audit key (base64, 32 bytes)
PASSKEYS_USER_HANDLE_SECRET Passkey secret (base64, 32 bytes)
VAULTIGEN_KEK_PROVIDER=environment + VAULTIGEN_KEK_KEYRING KEK keyring as JSON
VAULTIGEN_BACKUP_KEYRING Backup keyring

Files are preferred: environment values leak more easily through process listings and diagnostics.

Rotating the installation KEK

Rewrapping changes only how vault keys are wrapped; no entry data is rewritten.

php artisan vaultigen:kek:add      # add a new KEK and make it active
php artisan vaultigen:rotate-kek   # rewrap every vault key under it

Or use Rewrap installation key on System → Operations → Keys. Keep the old KEK in the keyring for as long as retained backups might need it, and update your escrow.

Rotating a vault key

Re-encrypts one vault’s entries, revisions and file keys under a new key version. Take a verified backup first; the operation refuses to retire the old version until a completed backup exists.

php artisan vaultigen:rotate-vault-key <vault-uuid-or-slug>
php artisan vaultigen:rotation:resume <rotation-run-uuid>   # only if it stopped part-way

Or use Rotate a vault key in the browser. Rotations run in restartable batches, are audited, and never overwrite a newer user edit.

Rotating the backup key

php artisan vaultigen:backup-key:rotate

Adds a new backup key and re-encrypts every retained set. Pass --retire-old-keys only after every off-host copy has been re-encrypted or can be discarded.

APP_KEY and the audit key

  • Do not rotate APP_KEY in place: stored authentication data would become unreadable. If you believe it is compromised, treat the host as compromised and restore from a clean backup.
  • Losing the audit key does not stop the application, but the existing chain can no longer be verified. Do not rotate it casually; old checkpoints cannot be re-signed.

Which key to rotate after an incident

Suspected exposure Action
kek.json Add a new KEK and rewrap.
One vault’s data Rotate that vault key, then rotate the external credentials in it.
backup.json Rotate the backup key; retire the old one once off-host copies are handled.
The whole host Stop the application, rotate the KEK and every vault key, restore from a backup taken before the compromise, and rotate external credentials.

Found a mistake? Tell us at [email protected].