Rotate secrets
Rotate the evidence chain key, change the receipt signing key and other credentials, and know which secrets must never change.
Rotate a secret on a schedule or after a suspected leak. Only the evidence chain key rotates with no side effects. Some secrets must never change.
Rotate the evidence chain key#
-
Create the new secret and store it in your secret manager.
Shell openssl rand -hex 32 -
Add the new key after the old one, and make it active.
.envConfig EVIDENCE_CHAIN_KEYS=2026a:<old secret>,2026b:<new secret> EVIDENCE_CHAIN_ACTIVE_KEY=2026b -
Restart every API replica.
Shell docker compose up -d consent-api
New events are sealed with 2026b. Each event records the id of the key that sealed it, so older events still verify.
Keep old chain keys in the list
Never remove a key while evidence sealed with it is kept. Those events would fail to verify.
Change the receipt signing key#
Avoid this. A deployment publishes and checks with one signing key at a time. After a change, older receipts and anchors verify only offline, with the earlier public key. They no longer pass Verify a receipt, POST /sdk/v1/receipts/verify or the signature check of Verify on an anchor.
If you must change it:
-
Save the public key set you publish now.
Shell curl -s https://consent.example.in/sdk/v1/keys \ > keys-until-2026-10.json -
Create a new seed and store it in your secret manager.
Shell openssl rand -base64 32 -
Set it as
RECEIPT_SIGNING_PRIVATE_KEY, with a newRECEIPT_SIGNING_KEY_ID. -
Restart the API.
-
Tell customers the date of the change and give them the saved key set.
Keep derived keys as dedicated keys#
A deployment that started with only ENCRYPTION_KEY derives its chain key and signing key from it. You can set the same values as dedicated keys, so nothing changes. Treat the output as a secret.
-
Print the
EVIDENCE_CHAIN_KEYSvalue. ForSOURCE_SECRET, useEVIDENCE_SIGNING_KEYif it is set, elseRECEIPT_SIGNING_KEY, elseENCRYPTION_KEY.Shell SOURCE_SECRET="$ENCRYPTION_KEY" node -e " const c = require('crypto'); const secret = Buffer.from(c.hkdfSync( 'sha256', process.env.SOURCE_SECRET, 'piedap-consent', 'evidence-chain-v2', 32, )).toString('hex'); const hash = c.createHash('sha256').update(secret).digest('hex'); console.log('k' + hash.slice(0, 10) + ':' + secret); " -
Print the
RECEIPT_SIGNING_PRIVATE_KEYvalue. ForSOURCE_SECRET, useRECEIPT_SIGNING_KEYif it is set, elseENCRYPTION_KEY.Shell SOURCE_SECRET="$ENCRYPTION_KEY" node -e " const c = require('crypto'); console.log(Buffer.from(c.hkdfSync( 'sha256', process.env.SOURCE_SECRET, 'piedap-consent', 'receipt-ed25519', 32, )).toString('base64')); " -
Set both values, and set
SUBJECT_HASH_PEPPERto the value ofENCRYPTION_KEY. -
Restart the API.
-
Check that
/sdk/v1/keysshows the samekidas before, and that an older record still shows its chain as verified.
Secrets you never change#
| Secret | Why |
|---|---|
SUBJECT_HASH_PEPPER |
Every stored identifier hash depends on it. With a new value no record can be found by identifier |
ENCRYPTION_KEY |
There is no tool to encrypt again. A new value makes stored secrets unreadable and every secret key invalid |
| A project's subject fingerprint key | Customers match receipts and webhooks with it. It is created once and kept |
Other credentials#
| Credential | How to change it |
|---|---|
PLATFORM_API_KEY, METRICS_TOKEN |
Set a new value and restart. Update your tooling and scraper |
| Database and Redis passwords | Change them in the service, update the variables and restart |
LOGIN_SECRET and the cookie names |
They follow IAM. Change both sides together |
| A customer's secret key | The customer revokes it on Install and creates a new one |
| A customer's identity secret | The customer rotates it on Install |
| A webhook signing secret | It cannot be changed. The customer creates a new endpoint and deletes the old one |