Implementing SPEKE v2.0 with AWS Elemental MediaPackage - AWS Elemental MediaPackage v2

Implementing SPEKE v2.0 with AWS Elemental MediaPackage

This topic supplements the SPEKE API v2 — Customizations and constraints to the DASH-IF specification with practical implementation guidance verified through production integration testing with AWS Elemental MediaPackage v2.

Note

This guide does not cover SPEKE API v2 — Content Key Encryption. Examples show content keys returned in pskc:PlainValue (cleartext base64), protected by HTTPS transport encryption.

HTTP protocol requirements

Request headers (from MediaPackage)

HeaderRequiredValue
Content-TypeYesapplication/xml
X-Speke-VersionYes2.0

Response headers (from key provider)

HeaderRequiredValue
Content-TypeYesapplication/xml
X-Speke-VersionYesEcho back the request value
X-Speke-User-AgentYesYour server identifier

Error responses (HTTP 422)

ConditionError message
Missing contentIdMissing CPIX@contentId
Missing versionMissing CPIX@version
Unsupported versionUnsupported CPIX@version
Missing encryption schemeMissing ContentKey@commonEncryptionScheme for KID {id}
Mixed encryption schemesNon compliant ContentKey@commonEncryptionScheme combination
Unsupported SPEKE versionUnsupported SPEKE version

CPIX document structure

Every SPEKE v2.0 exchange uses CPIX v2.3 XML with the namespaces xmlns:cpix="urn:dashif:org:cpix" and xmlns:pskc="urn:ietf:params:xml:ns:keyprov:pskc".

Root element: cpix:CPIX

AttributeRequiredDescription
contentIdYesContent identifier from MediaPackage. Must not be empty.
versionYesMust be 2.3. Return error if missing or unsupported.

The key provider must NOT modify contentId or version in the response. However, the key provider may override the kid value on ContentKey elements — see Overriding the key identifier.

ContentKeyList (mandatory)

Contains one or more ContentKey elements. Each represents an encryption key.

AttributeRequiredDescription
kidYesKey ID in UUID format
commonEncryptionSchemeYesOne of: cenc, cbc1, cbcs
explicitIVOptionalBase64-encoded 16-byte initialization vector

All ContentKey elements in a single request must use the same commonEncryptionScheme. The response must include cpix:Data/pskc:Secret/pskc:PlainValue with a base64-encoded 16-byte AES key.

DRMSystemList (mandatory)

Contains one DRMSystem element per key per DRM system. Example: 2 keys x 3 DRM systems = 6 DRMSystem elements.

AttributeRequiredDescription
kidYesKey ID this DRM system applies to
systemIdYesDRM system UUID

Mandatory HLS signaling rules:

  1. All HLSSignalingData content must be base64-encoded.

  2. Both playlist="media" and playlist="master" must be present.

  3. Decoded media and master tags must have identical attributes. Only the tag name differs.

  4. HLS tags must NOT contain a SCHEME attribute.

  5. Each DRMSystem must reference exactly one kid with exactly one HLS tag.

ContentKeyPeriodList (live streaming)

Must be echoed back unchanged from the request. MediaPackage sends this even without key rotation.

ContentKeyUsageRuleList (mandatory)

Defines which keys protect which tracks. The AWS SPEKE v2 spec explicitly requires the key provider to echo back the ContentKeyUsageRuleList unchanged — MediaPackage defines the encryption contract (which keys protect which tracks), and the DRM server must not alter it.

AttributeRequiredDescription
kidYesKey ID this rule applies to
intendedTrackTypeYesALL, VIDEO, AUDIO, SD, HD, UHD, etc.

At least one VideoFilter or AudioFilter child element is required.

DRM system signaling

System IDs

System IDReferenceEncryption Schemes
94ce86fb-07ff-4f43-adb8-93d2fa968ca2FairPlaycbcs only
edef8ba9-79d6-4ace-a3c8-27dcd51d21edWidevinecenc, cbcs
9a04f079-9840-4286-ab92-e65be0885f95PlayReadycenc, cbcs
3ea8778f-7742-4bf9-b18b-e834b2acbd47Clear Key AES-128cbc1 only

FairPlay signaling

Response XML structure:

<cpix:DRMSystem kid="8fe6f4de-..." systemId="94ce86fb-07ff-4f43-adb8-93d2fa968ca2"> <cpix:HLSSignalingData playlist="media">BASE64_ENCODED_EXT_X_KEY</cpix:HLSSignalingData> <cpix:HLSSignalingData playlist="master">BASE64_ENCODED_EXT_X_SESSION_KEY</cpix:HLSSignalingData> </cpix:DRMSystem>

FairPlay uses only HLS signaling (no PSSH or ContentProtectionData).

Decoded HLS signaling (media and master identical except tag name):

#EXT-X-KEY:METHOD=SAMPLE-AES,URI="skd://<BASE64_JSON>",KEYFORMAT="com.apple.streamingkeydelivery",KEYFORMATVERSIONS="1" #EXT-X-SESSION-KEY:METHOD=SAMPLE-AES,URI="skd://<BASE64_JSON>",KEYFORMAT="com.apple.streamingkeydelivery",KEYFORMATVERSIONS="1"

The skd:// URI contains a base64-encoded JSON payload — NOT just the kid hex:

{"ContentId":"101","KeyId":"8fe6f4de-841a-3acd-a81a-be516f356bbb","IV":"base64-encoded-IV=="}
Important

The KeyId must use UUID format with hyphens. Do NOT include a separate IV=0x... attribute in the HLS tag — the IV is inside the JSON payload only.

Widevine signaling

Response XML structure:

<cpix:DRMSystem kid="dda7afc9-..." systemId="edef8ba9-79d6-4ace-a3c8-27dcd51d21ed"> <cpix:PSSH>BASE64_ENCODED_PSSH_BOX</cpix:PSSH> <cpix:ContentProtectionData>BASE64_ENCODED_CENC_PSSH_XML</cpix:ContentProtectionData> <cpix:HLSSignalingData playlist="media">BASE64_ENCODED_EXT_X_KEY</cpix:HLSSignalingData> <cpix:HLSSignalingData playlist="master">BASE64_ENCODED_EXT_X_SESSION_KEY</cpix:HLSSignalingData> </cpix:DRMSystem>

Widevine includes PSSH box, ContentProtectionData, and HLS signaling. All values are base64-encoded.

PSSH Box: Base64-encoded binary data containing the Widevine PSSH box. The box structure is:

[4 bytes] Box size (big-endian uint32) [4 bytes] Box type: "pssh" [1 byte] Version: 0 [3 bytes] Flags: 0 [16 bytes] System ID: edef8ba9-79d6-4ace-a3c8-27dcd51d21ed (Widevine) [4 bytes] Data size (big-endian uint32) [N bytes] Widevine PSSH data (protobuf-encoded, contains key IDs and provider)

ContentProtectionData (base64-decoded): Must use the standard CENC PSSH XML format:

<cenc:pssh xmlns:cenc="urn:mpeg:cenc:2013">PSSH_BOX_BASE64</cenc:pssh>

The PSSH_BOX_BASE64 inside cenc:pssh is the same base64-encoded PSSH box as in the cpix:PSSH element.

Decoded HLS signaling: The URI must be a data URI with the PSSH box embedded inline:

#EXT-X-KEY:METHOD=SAMPLE-AES,URI="data:text/plain;base64,<PSSH_BOX_BASE64>",KEYFORMAT="urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed",KEYFORMATVERSIONS="1"
Important

The URI must be a data:text/plain;base64, data URI containing the PSSH box — NOT a license server URL (e.g., https://widevine.example.com/license). ContentProtectionData must use the cenc:pssh format — NOT custom wrappers like <ContentProtectionData><PSSH>...</PSSH></ContentProtectionData>. Do NOT include a SCHEME attribute in HLS tags.

PlayReady signaling

Response XML structure:

<cpix:DRMSystem kid="566209fc-..." systemId="9a04f079-9840-4286-ab92-e65be0885f95"> <cpix:PSSH>BASE64_ENCODED_PSSH_BOX</cpix:PSSH> <cpix:ContentProtectionData>BASE64_ENCODED_CENC_PSSH_AND_MSPR_PRO</cpix:ContentProtectionData> <cpix:HLSSignalingData playlist="media">BASE64_ENCODED_EXT_X_KEY</cpix:HLSSignalingData> <cpix:HLSSignalingData playlist="master">BASE64_ENCODED_EXT_X_SESSION_KEY</cpix:HLSSignalingData> </cpix:DRMSystem>

PlayReady includes PSSH box, ContentProtectionData (with both CENC and PRO elements), and HLS signaling.

PSSH Box: Base64-encoded binary data containing the PlayReady PSSH box. The box structure is:

[4 bytes] Box size (big-endian uint32) [4 bytes] Box type: "pssh" [1 byte] Version: 0 [3 bytes] Flags: 0 [16 bytes] System ID: 9a04f079-9840-4286-ab92-e65be0885f95 (PlayReady) [4 bytes] Data size (big-endian uint32) PlayReady Object (PRO) record: [4 bytes] Record count (little-endian uint32, always 1) [4 bytes] Record size (little-endian uint32) [N bytes] WRM Header XML (UTF-16LE encoded)

The WRM Header XML contains KID, CHECKSUM, LA_URL (license acquisition URL), ALGID, and KEYLEN elements for each content key.

ContentProtectionData (base64-decoded): Must contain BOTH cenc:pssh AND mspr:pro elements:

<cenc:pssh xmlns:cenc="urn:mpeg:cenc:2013">PSSH_BASE64</cenc:pssh> <mspr:pro xmlns:mspr="urn:microsoft:playready">PRO_RECORD_BASE64</mspr:pro>

The cenc:pssh contains the full PSSH box. The mspr:pro contains the PlayReady Object record (count + size + WRM Header XML in UTF-16LE).

Decoded HLS signaling (attribute order: METHOD, KEYFORMAT, KEYFORMATVERSIONS, URI): The data URI must use charset=UTF-16 and contain the PlayReady Object record (PRO), NOT the raw PSSH box:

#EXT-X-KEY:METHOD=SAMPLE-AES,KEYFORMAT="com.microsoft.playready",KEYFORMATVERSIONS="1",URI="data:text/plain;charset=UTF-16;base64,<PRO_RECORD_BASE64>"
Important

The data URI must use charset=UTF-16 and embed the PlayReady Object record, NOT the raw PSSH box. The PRO record structure: count(4 bytes LE) + size(4 bytes LE) + WRM Header XML (UTF-16LE). Attribute order must be METHOD, KEYFORMAT, KEYFORMATVERSIONS, URI. ContentProtectionData must include BOTH cenc:pssh AND mspr:pro. Omitting either will cause failures. Do NOT include a SCHEME attribute.

Clear Key AES-128 signaling

Response XML structure:

<cpix:DRMSystem kid="136436ea-..." systemId="3ea8778f-7742-4bf9-b18b-e834b2acbd47"> <cpix:HLSSignalingData playlist="media">BASE64_ENCODED_EXT_X_KEY</cpix:HLSSignalingData> <cpix:HLSSignalingData playlist="master">BASE64_ENCODED_EXT_X_SESSION_KEY</cpix:HLSSignalingData> </cpix:DRMSystem>

Clear Key AES-128 uses only HLS signaling (no PSSH or ContentProtectionData).

Decoded HLS signaling:

#EXT-X-KEY:METHOD=AES-128,URI="https://<key_server>/client/<content_id>/<kid>",KEYFORMAT="identity",KEYFORMATVERSIONS="1"

The key delivery endpoint must return the raw 16-byte binary key (not base64) with CORS headers. No DRM license server is needed — the player fetches the key directly.

Key delivery to players

DRM SystemPlayer-side key deliveryLicense server required
Clear Key AES-128Player GETs raw 16-byte key from HTTPS URL in manifestNo
FairPlayPlayer contacts FPS license server using skd:// URIYes (Apple FPS license)
WidevinePlayer uses EME API with PSSH from data URIYes (Google/3rd party)
PlayReadyPlayer uses EME API with PRO from data URIYes (Microsoft/3rd party)

Testing and verification

Verify your SPEKE v2.0 implementation
  1. Test SPEKE endpoint directly. Send a POST request with a CPIX XML body and verify the response contains contentId, version="2.3", PlainValue with 16-byte base64 key, explicitIV, base64-encoded HLSSignalingData, and echoed ContentKeyPeriodList.

  2. Verify key determinism. Send the same request twice. The PlainValue should be identical both times.

  3. Verify HLS signaling. Decode the base64 HLSSignalingData and verify: FairPlay uses skd:// with JSON payload, Widevine uses data:text/plain;base64, with PSSH, PlayReady uses data:text/plain;charset=UTF-16;base64, with PRO, no SCHEME attribute, media and master tags match.

  4. Test with MediaPackage. Configure a MediaPackage endpoint with SPEKE encryption, start a live stream, check server logs for requests, and verify playback.

Implementation checklist

  • HTTP endpoint accepts POST with Content-Type: application/xml

  • Validates and echoes X-Speke-Version header

  • Returns X-Speke-User-Agent response header

  • Validates contentId, version, commonEncryptionScheme

  • Returns HTTP 422 with exact error messages per SPEKE specification

  • Generates deterministic 16-byte AES keys with explicitIV

  • Generates per-kid DRMSystem elements

  • Base64-encodes all HLSSignalingData, PSSH, and ContentProtectionData

  • FairPlay: skd:// URI contains base64-encoded JSON with ContentId, KeyId (with hyphens), and IV

  • Widevine: HLS URI uses data:text/plain;base64,<PSSH>; ContentProtectionData uses cenc:pssh

  • PlayReady: HLS URI uses data:text/plain;charset=UTF-16;base64,<PRO>; ContentProtectionData includes both cenc:pssh and mspr:pro; attribute order METHOD, KEYFORMAT, KEYFORMATVERSIONS, URI

  • Clear Key AES-128: provides GET endpoint returning raw 16-byte key with CORS headers

  • HLS media and master tags have identical attributes; no SCHEME attribute

  • Echoes ContentKeyPeriodList and ContentKeyUsageRuleList unchanged

  • Includes VideoFilter and/or AudioFilter in usage rules