> ## Documentation Index
> Fetch the complete documentation index at: https://docs.blindcast.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Key Rotation

> Rotate master keys without downtime: dual-key deployment, re-encryption with the CLI, and safe key removal.

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](/key-server/leases) instead)

## How rotation works

```mermaid theme={null}
sequenceDiagram
  participant Admin
  participant KS as Key Server
  participant CLI as CLI
  participant S3 as S3

  Note over Admin,S3: Phase 1: Deploy both keys
  Admin->>KS: Add NEW_MASTER_KEY + NEW_SALT
  Note over KS: Serves keys from OLD first, falls back to NEW

  Note over Admin,S3: Phase 2: Re-encrypt content
  CLI->>S3: Download encrypted segments
  CLI->>CLI: Decrypt with OLD key, re-encrypt with NEW key
  CLI->>S3: Upload re-encrypted segments

  Note over Admin,S3: Phase 3: Verify playback
  Admin->>KS: Test re-encrypted content plays correctly

  Note over Admin,S3: Phase 4: Remove old key
  Admin->>KS: Remove OLD_MASTER_KEY
  Note over KS: Serves keys from NEW only
```

## Step-by-step

### 1. Generate a new master key

```bash theme={null}
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:

```bash theme={null}
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:

```bash theme={null}
# 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:

```bash theme={null}
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:

```bash theme={null}
# 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:

```bash theme={null}
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:

```bash theme={null}
# 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.
