The key hierarchy
Ingest-time key generation
At the moment your ingest pipeline receives a blob:- Generate
K_blob— a random 32-byte XChaCha20-Poly1305 key. - Encrypt the blob with
K_blob— use a fresh 24-byte nonce. - Compute the BLAKE3 hash of the ciphertext — this is the content-addressed identifier the CDN sees.
- Upload the ciphertext to your origin backend.
- Store
K_blobin your app server’s key store, keyed by the ciphertext hash. - Insert
(hash, bucket, key, size, content_type)into the catalog.
Epoch keys
The app server rotates an epoch key every 5 minutes. At any given time:- The current epoch key is used to wrap
K_blobfor new play requests. - The previous epoch key is still accepted for a grace period (covers requests in flight during rotation).
- Older epoch keys are retired.
cdn/keys/v1 stream to revoke — within one epoch, the client can no longer unwrap new envelopes for new hash requests.
Client decryption flow
Offline leases
The app server can issue a sealed envelope that the client stores locally for offline playback:- A specific device ID
- An expiration timestamp
- A per-device play count
What CDN nodes see vs. don’t
| Party | Sees ciphertext? | Sees K_blob? | Sees epoch keys? |
|---|---|---|---|
| CDN node | ✅ | ❌ | ❌ |
| App server | ✅ (if also hosting ingest) | ✅ | ✅ |
| Client | ✅ | ✅ (only for authorized hashes) | ✅ |
What compromise looks like
- Single CDN node compromised — attacker gets ciphertext. Useless without
K_blob. - Client device compromised — attacker gets plaintext for content the client has already played, plus whatever offline leases are valid. Limited to that one user’s catalog.
- App server compromised — attacker gets
K_blobfor every blob in your catalog. Catastrophic. This is the single trust anchor and should be treated accordingly.
- Separate key-store infrastructure (Vault, KMS) from the auth/billing layer; compromise of the web tier should not grant key-store access.
- Rotate the epoch key root on any suspicion of compromise.
- Forward secrecy for stored
K_blobmaterial is an open item — epoch keys provide session-level forward secrecy, but a long-livedK_blobcompromise remains catastrophic.
Accepted trade-offs
- No forward secrecy for stored
K_blob. Epoch key rotation limits access-control blast radius (5 minutes), not forward secrecy of historical content. Content previously delivered with a compromisedK_blobremains decryptable. - App server is a trust root. There is no protocol-level mitigation for app server compromise — operational security is the mitigation.