Skip to content

App Secret Rotation #55

@supun-io

Description

@supun-io

We use the APP_SECRET variable for a couple of things:

  • to encrypt data before saving in the database
  • to hash (HMAC) sensitive data that is used for checking uniqueness (for example, IP addresses in Hyvor Talk)

How to rotate

If our app secret is leaked, we need to be ready to rotate it. This is done in two steps.

Before rotation, we have this:

APP_SECRET=key1

As the first step, we set APP_SECRET to the new key and APP_SECRET_PREVIOUS to the old key.

APP_SECRET=key2
APP_SECRET_PREVIOUS=key1

How we handle encryption and hashing is explained below when we have two keys.

Then, we run the following command on one of the servers:

bin/console rotate:key

It emits the following event:

class RotateAppSecretKeyEvent
{
     public function __construct(
           public string $newKey,
           public string $previousKey,
           public OutputInterface $output, // to log messages to the console (such as what's being handled)
     ) {}
}

App-side handling

On the app side, we have to handle things carefully to support rotating:

1. Encryption

We should always use the current APP_SECRET when encrypting.

2. Decryption

We should try to decrypt by both keys if APP_SECRET_PREVIOUS is present:

(we might need a helper for encrypting in the internal lib)

try {
    return decryptBySecretKey($payload);
}  catch (DecryptionFailedException) {
    return decryptBySecretKeyPrevious($payload);
}

3. Hashing (HMAC)

Similarly to encrypting, always use the current APP_SECRET when hashing.

4. Hash Verifying

(For example, in a query like WHERE ip_hash = :hash)

We should check with both keys if they are present:

WHERE ip_hash = :hashWithAppSecret
WHERE ip_hash = :hashWithAppSecretPrevious

If we find at least one, we have that record in the database.

5. Rotating keys

bin/console rotate:key dispatches the RotateAppSecretKeyEvent but it does not do anything by itself. The app should have listeners that actually handles the rotation. You need to

  • get all encrypted data, decrypt them using the old key, re-encrypt them from the new key
  • get all hashed data

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions