Skip to main content
Key rotation replaces your master key without downtime or re-uploading content. The process uses a dual-key period where the key server can derive keys from both the old and new master key.

When to rotate

Rotate your master key when:
  • A team member with access to the key leaves the organization
  • Your secret manager audit requires periodic rotation
  • You suspect the key may have been exposed
  • Compliance requirements mandate rotation (e.g., PCI DSS)
You do not need to rotate the key:
  • After routine deployments
  • When adding new content (each content ID gets a unique derived key)
  • When revoking individual viewer access (use leases instead)

How rotation works

Step-by-step

1. Generate a new master key

blindcast keygen
# BLINDCAST_MASTER_KEY=<new-key-hex>
# BLINDCAST_SALT=<new-salt-hex>
Store the new key and salt in your secret manager.

2. Deploy with dual keys

Update the key server to accept both old and new keys:
docker run -d \
  -e MASTER_KEY_HEX=<new-key-hex> \
  -e SALT_HEX=<new-salt-hex> \
  -e FALLBACK_MASTER_KEY_HEX=<old-key-hex> \
  -e FALLBACK_SALT_HEX=<old-salt-hex> \
  -e CORS_ORIGINS=https://app.example.com \
  -p 4100:4100 \
  blindcast/keyserver
During the dual-key period:
  • New content uses the new key
  • Old content still works — the key server tries the primary key first, then falls back to the old key
  • Players don’t need any changes

3. Re-encrypt existing content

Use the CLI to re-encrypt content with the new key:
# Re-encrypt a single content item
blindcast encrypt ./segments \
  --content-id vid-001 \
  --key <new-key-hex> \
  --salt <new-salt-hex> \
  --old-key <old-key-hex> \
  --old-salt <old-salt-hex>

# Upload re-encrypted segments
blindcast upload ./segments \
  --bucket my-video-bucket \
  --prefix content/vid-001
For bulk re-encryption, script it:
set -euo pipefail

for content_id in $(cat content-ids.txt); do
  echo "Re-encrypting ${content_id}..."
  blindcast encrypt "./segments/${content_id}" \
    --content-id "$content_id" \
    --key "$NEW_KEY" \
    --salt "$NEW_SALT" \
    --old-key "$OLD_KEY" \
    --old-salt "$OLD_SALT"

  blindcast upload "./segments/${content_id}" \
    --bucket my-video-bucket \
    --prefix "content/${content_id}"

  echo "Done: ${content_id}"
done

4. Verify playback with the new key

Before removing the old key, verify that re-encrypted content plays back correctly:
# Test a re-encrypted content item
blindcast serve &
# Open your player and load the re-encrypted content
# Verify playback works end-to-end
Test at least one content item per content type (different resolutions, durations, etc.) before proceeding.

5. Remove the old key

Once all content has been re-encrypted and verified:
docker run -d \
  -e MASTER_KEY_HEX=<new-key-hex> \
  -e SALT_HEX=<new-salt-hex> \
  -e CORS_ORIGINS=https://app.example.com \
  -p 4100:4100 \
  blindcast/keyserver
Remove FALLBACK_MASTER_KEY_HEX and FALLBACK_SALT_HEX. Delete the old key from your secret manager.

CDN cache invalidation

After re-encrypting content, invalidate the CDN cache for the affected segments:
# CloudFront
aws cloudfront create-invalidation \
  --distribution-id E1234567890 \
  --paths "/content/vid-001/*"

# Cloudflare
curl -X POST "https://api.cloudflare.com/client/v4/zones/{zone_id}/purge_cache" \
  -H "Authorization: Bearer {token}" \
  -d '{"prefixes": ["content/vid-001/"]}'
Manifests (.m3u8) should already have short cache TTLs, so they’ll pick up the new EXT-X-KEY URI automatically.

Safety notes

  • Never delete the old key before re-encryption is complete. Content encrypted with the old key becomes permanently inaccessible.
  • Test on a single content item first. Re-encrypt one video, verify playback, then proceed with the rest.
  • Keep the dual-key period short. The longer both keys are active, the larger the window of exposure if the old key was compromised.
  • The salt can stay the same. If you only need to rotate the master key (not the salt), you can reuse the existing salt. However, rotating both is recommended.