Key Rotation

Key rotation replaces your application's RSA key pair with a new one. This is useful for security compliance, suspected compromise, or regular key hygiene.

When to Rotate Keys

  • Suspected compromise - If you believe the private key may have been exposed
  • Employee departure - When someone with key access leaves the team
  • Compliance requirements - Some security standards require periodic rotation
  • Regular maintenance - Best practice to rotate keys annually

What Happens During Rotation

When you rotate keys, Blackwalnut:

  1. Generates a new 4096-bit RSA key pair
  2. Replaces the existing keys with the new ones
  3. Re-signs all active licenses with the new private key
  4. Returns the new public key and count of re-signed licenses

Note: Expired and revoked licenses are not re-signed, as they are no longer valid anyway.

Rotating Keys

POST /api/v1/apps/:slug/rotate-keys
$ curl -X POST https://license.yourapp.com/api/v1/apps/my-app/rotate-keys \
  -H "Authorization: Bearer sk_live_..."

# Response
{
  "data": {
    "message": "Keys rotated successfully",
    "licenses_resigned": 1234,
    "public_key_pem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A...\n-----END PUBLIC KEY-----"
  }
}

After Rotation

After rotating keys, you must update your software to use the new public key. Existing tokens signed with the old key will fail verification.

Options for Distribution

1. Runtime Fetch (Recommended)

If your software fetches the public key at runtime, it will automatically get the new key. No software update required.

2. Software Update

If the key is bundled in your binary, release a new version with the updated public key.

3. Grace Period

For bundled keys, consider supporting both old and new keys temporarily to allow users time to update.

Grace Period Implementation

To support a smooth transition, try the new key first, then fall back to the old:

verify_with_fallback.py
import jwt

CURRENT_KEY = """-----BEGIN PUBLIC KEY-----
NEW_KEY_HERE
-----END PUBLIC KEY-----"""

PREVIOUS_KEY = """-----BEGIN PUBLIC KEY-----
OLD_KEY_HERE
-----END PUBLIC KEY-----"""

def verify_license(token):
    # Try current key first
    for key in [CURRENT_KEY, PREVIOUS_KEY]:
        try:
            payload = jwt.decode(
                token,
                key,
                algorithms=["RS256"],
                audience="my-app"
            )
            return {"valid": True, **payload}
        except jwt.InvalidSignatureError:
            continue  # Try next key
        except jwt.InvalidTokenError as e:
            return {"valid": False, "error": str(e)}

    return {"valid": False, "error": "invalid_signature"}

Notifying Customers

After key rotation, all active licenses have new tokens. You may want to:

  • Send customers their new license tokens via email
  • Provide a license portal where they can download updated tokens
  • Use your application's update mechanism to push new tokens
Fetch updated license
# Get the updated token for a license
$ curl https://license.yourapp.com/api/v1/apps/my-app/licenses/lic_abc123 \
  -H "Authorization: Bearer sk_live_..."

# Response includes the new token
{
  "data": {
    "id": "lic_abc123",
    "token": "eyJhbGciOiJSUzI1NiIs...",
    ...
  }
}

Best Practices

  • Schedule rotation during low-traffic periods
  • Have a rollout plan for distributing the new public key
  • Consider runtime key fetching for easier rotation
  • Keep the previous key functional for a grace period if using bundled keys
  • Document your rotation in an audit log
  • Test the rotation process in staging first

Related Topics

After key rotation, all active licenses are automatically re-signed with the new key. For more control over license token updates, see the License Regeneration guide.