AWS generates a KeyID automatically but it is hard to get this KeyID. With Axinom Key Service Speke endpoint you can override the KeyID to have a deterministic value.

SPEKE KeyID Override Functionality

Context

Amazon Web Services (AWS) Elemental services (MediaConvert and MediaPackage) utilize the SPEKE protocol to request encryption keys. These keys are referenced by key IDs (KIDs) that are deterministically generated by AWS, based on the resource ID and other factors (such as the key period index, in case of key rotation).

Problem

The exact algorithm, however, is not public, preventing the KIDs from being known in advance. However, knowing the KIDs is required for authorizing specific keys via the Axinom DRM Entitlement Message sent to the license services.

The inability to predict the KIDs leaves users with choices that may be inconvenient, depending on the overall solution architecture, such as:

  • setting up a proxy between AWS and Axinom Key Service to capture the key IDs

  • analyzing the created media manifests after packaging.

Therefore, Axinom Key Service provides a key override feature that replaces the AWS-generated KIDs with new values generated according to a known algorithm, making it possible to predict them in advance.

Tip
Another workaround for this problem is described on the key ID extraction page.

Solution

Enabling the Key ID Override Functionality

Key override can be enabled by adding the overrideKeyIds=true query parameter to the Axinom Key Service SPEKE endpoint URL when setting up the AWS API Gateway. For example:

For SPEKE v1:
https://key-server-management.axprod.net/api/Speke?overrideKeyIds=true

For SPEKE v2:
https://key-server-management.axprod.net/api/SpekeV2?overrideKeyIds=true

Key ID Derivation Algorithm

When key ID override is enabled, the KIDs can be predicted according to the key ID derivation algorithm and the examples presented below. Because there are two versions of the SPEKE protocol with different functionality there are also two versions of the key ID derivation algorithm. Make sure to use the right one, depending on the SPEKE version you are using with AWS.

SPEKE v1

In simple terms, the KID is derived by hashing the following known values:

KID = hash(TenantId + Resource ID + Content Key Period Index + KID Index)

In the example above, the values include:

  • TenantId - customer’s tenant ID

  • AWS Resource ID - resource ID used in AWS MediaConvert or MediaPackage

  • Content Key Period Index - When key rotation is enabled, each SPEKE v1 key request contains a zero-based sequential period index number. Use “0” when no key rotation enabled in Media Package or when using MediaConvert.

  • KID Index - the Key ID index in the request. In general, it should be fixed to “0”, as the AWS SPEKE v1 protocol currently specifies only one key per request. However, our implementation also supports multiple keys. Therefore, it may prove useful in custom scenarios.

Hashing Function

The hashing function, which is used to hash the above-mentioned values, is a customized function based on SHA256. As the SHA256 hashing function produces 256 bits, an additional XOR operation is performed to reduce the output to 128 bits which can be easily converted to a GUID.

See an example below:

// Get the SHA256 bytes.
BaseHash = SHA256("<TenantId>" + "<AWS Resource ID>" + "<Content Key Period Index>" + "0")

PartOneHash = Take first 16 bytes of BaseHash
PartTwoHash = Take the last 16 bytes of BaseHash

XorResult = PartOneHash XOR PartTwoHash

// Content Key ID
// GUID must be generated using little-endian byte order as in Microsoft .NET.
KID = XorResult to GUID

Python & NodeJS implementation examples are provided below. However, any language can be used to implement the algorithm.

Sample Key Derivation Algorithm Implementation

Python 3

See an example of Python implementation below.

import hashlib
import uuid

tenantId = "10d42897-a795-4fd8-a2d4-00e3ab59dece"
awsResourceId = "bd99b041-4353-4b7a-9533-f36ee752b735"
contentKeyPeriodIndex = "0"

baseToHashAsBytes = str.encode(tenantId + awsResourceId + contentKeyPeriodIndex + "0")

# Create a SHA256 instance.
hash = hashlib.sha256()
hash.update(baseToHashAsBytes)

baseHashValue = hash.digest()

# Get the first 16 bytes of the base hash.
hashPartOne = baseHashValue[:16]

# Get the last 16 bytes of the base hash.
hashPartTwo = baseHashValue[16:32]

# XOR the first and second parts to make a 16 bytes hash.
xorHash = bytes([_a ^ _b for _a, _b in zip(hashPartOne, hashPartTwo)])

# Create a GUID based on little-endian bytes.
kid = uuid.UUID(bytes_le=xorHash)

print(kid) # KID=0a1e610d-e346-0665-42b2-409580b51be6

NodeJS

No 3rd party modules required for the NodeJS implementation.

const crypto = require("crypto");

const tenantId = "10d42897-a795-4fd8-a2d4-00e3ab59dece"
const awsResourceId = "bd99b041-4353-4b7a-9533-f36ee752b735"
const contentKeyPeriodIndex = "0"

var buffer = Buffer.from(tenantId + awsResourceId + contentKeyPeriodIndex + "0");

console.log(buffer.toString());

// Create SHA256 hash object.
var sha256Hasher = crypto.createHash("sha256");

sha256Hasher.update(buffer);

const baseHashValue = sha256Hasher.digest();

const partOne = baseHashValue.slice(0, 16);
const partTwo = baseHashValue.slice(16, 32);

let xorHash = Buffer.alloc(16);

// XOR first and second parts of the base hash.
for (let n = 0; n < 16; n++)
    xorHash[n] = partOne[n] ^ partTwo[n % partTwo.length];

// Reverse endianess.
const partA = xorHash.slice(0, 4).reverse();
const partB = xorHash.slice(4, 6).reverse();
const partC = xorHash.slice(6, 8).reverse();

// Build a GUID string.
const kid = partA.toString('hex') + '-' +
    partB.toString('hex') + '-' +
    partC.toString('hex') + '-' +
    xorHash.slice(8, 10).toString('hex') + '-' +
    xorHash.slice(10, 16).toString('hex');

console.log(kid); // KID=0a1e610d-e346-0665-42b2-409580b51be6

AWS SPEKE v1 Request Variations

Based on the AWS Elemental service use, i.e. MediaConvert or MediaPackage, the SPEKE v1 requests are slightly different.

The MediaConvert service which is used to encode VOD content does not contain any ContentKeyPeriodelements in the request which is used in key rotation. Therefore, when deriving the KID, use 0 for the contentKeyPeriodIndex value as the SPEKE v1 endpoint assumes the content key period index as zero when not present.

The MediaPackage service which also provides key rotation functionality always contains a ContentKeyPeriodelement with the index 0 even if key rotation is not enabled Therefore, when key rotation is disabled, both MediaConvert and MediaPackage get the same overridden KID from the Axinom Key Service SPEKE v1 endpoint.

Key Rotation

When key rotation is enabled, the AWS MediaPackage requests a KID from the SPEKE v1 endpoint for each key period. When the override KIDs functionality is enabled in the key service, the KIDs are overridden based on their content key period index. For each SPEKE request, the content key period index sequentially increments by one. For example:

<cpix:ContentKeyPeriod id="keyPeriod_22ebd583-732e-4ab3-9393-7ad173355736" index="0"/>

<cpix:ContentKeyPeriod id="keyPeriod_22ebd583-732e-4ab3-9393-7ad173355736" index="1"/>

<cpix:ContentKeyPeriod id="keyPeriod_22ebd583-732e-4ab3-9393-7ad173355736" index="2"/>

...

<cpix:ContentKeyPeriod id="keyPeriod_22ebd583-732e-4ab3-9393-7ad173355736" index="999"/>

It is up to the user to map the indexes and thereby the generated key IDs to specific time periods, if necessary. For example, the user could do it based on knowing the key rotation period and the stream start time.

SPEKE v2

As of writing this documentation, the SPEKE v2 protocol is only supported by AWS MediaPackage. Once AWS expands SPEKE v2 to MediaConvert we will update this documentation with new information.

In simple terms, the KID is derived by hashing the following known values:

KID = hash(TenantId + Resource ID + Protection Scheme + Content Key Period Index + Intended Track Type)

In the example above, the values include:

  • TenantId - customer’s tenant ID

  • Resource ID - resource ID used in AWS MediaPackage

  • Protection Scheme – “cenc” or “cbcs” encryption scheme used for packaging content. Currently, AWS doesn’t allow to set the protection scheme explicitly. Therefore, DASH-ISO packaging will always result in “cenc” and Apple HLS and CMAF will result in “cbcs“ being applied.

  • Content Key Period Index - When key rotation is enabled, each SPEKE v2 key request will contain a zero-based sequential period index number. Use “0” when key rotation is not enabled in MediaPackage.

  • Intended Track Type – When multiple keys are requested, the intended-track-type shall be set. Currently, AWS will always set “VIDEO“ for the first content key and “AUDIO” for the second content key.

All the parameters are string values.

Hashing Function

The hashing function, which is used to hash the above-mentioned values, is a customized function based on SHA256. As the SHA256 hashing function produces 256 bits, an additional XOR operation is performed to reduce the output to 128 bits which can be easily converted to a GUID.

See an example below:

// Get the SHA256 bytes.
BaseHash = SHA256("<TenantId>" + "<AWS Resource ID>" + "<Protection Scheme>" + "<Content Key Period Index>" + "<Intended Track Type>")

PartOneHash = Take first 16 bytes of BaseHash
PartTwoHash = Take the last 16 bytes of BaseHash

XorResult = PartOneHash XOR PartTwoHash

// Content Key ID
// GUID must be generated using little-endian byte order as in Microsoft .NET.
KID = XorResult to GUID

Python & NodeJS implementation examples are provided below, but any language can be used to implement the algorithm.

Sample Key Derivation Algorithm Implementation

Python 3

See an example of a Python implementation below.

import hashlib
import uuid

tenantId = "10d42897-a795-4fd8-a2d4-00e3ab59dece"
contentId = "test_content"
protectionScheme = "cenc"
contentKeyPeriodIndex = "0"
intendedTrackType = "VIDEO"

baseToHashAsBytes = str.encode(tenantId + contentId + protectionScheme + contentKeyPeriodIndex + intendedTrackType)

# Create a SHA256 instance.
hash = hashlib.sha256()
hash.update(baseToHashAsBytes)

baseHashValue = hash.digest()

# Get the first 16 bytes of the base hash.
hashPartOne = baseHashValue[:16]

# Get the last 16 bytes of the base hash.
hashPartTwo = baseHashValue[16:32]

# XOR the first and second parts to make a 16 bytes hash.
xorHash = bytes([_a ^ _b for _a, _b in zip(hashPartOne, hashPartTwo)])

# Create a GUID based on little-endian bytes.
kid = uuid.UUID(bytes_le=xorHash)

print(kid) # KID=bc8b57c8-6a1e-1b58-5235-d8be6ce5602a

NodeJS

No 3rd party modules required for the NodeJS implementation.

const crypto = require("crypto");

const tenantId = "10d42897-a795-4fd8-a2d4-00e3ab59dece"
const contentId = "test_content"
const protectionScheme = "cenc"
const contentKeyPeriodIndex = "0"
const intendedTrackType = "VIDEO"

var buffer = Buffer.from(tenantId + contentId + protectionScheme + contentKeyPeriodIndex + intendedTrackType);

// Create SHA256 hash object.
var sha256Hasher = crypto.createHash("sha256");

sha256Hasher.update(buffer);

const baseHashValue = sha256Hasher.digest();

const partOne = baseHashValue.slice(0, 16);
const partTwo = baseHashValue.slice(16, 32);

let xorHash = Buffer.alloc(16);

// XOR first and second parts of the base hash.
for (let n = 0; n < 16; n++)
    xorHash[n] = partOne[n] ^ partTwo[n % partTwo.length];

// Reverse endianess.
const partA = xorHash.slice(0, 4).reverse();
const partB = xorHash.slice(4, 6).reverse();
const partC = xorHash.slice(6, 8).reverse();

// Build a GUID string.
const kid = partA.toString('hex') + '-' +
    partB.toString('hex') + '-' +
    partC.toString('hex') + '-' +
    xorHash.slice(8, 10).toString('hex') + '-' +
    xorHash.slice(10, 16).toString('hex');

console.log(kid); // KID=bc8b57c8-6a1e-1b58-5235-d8be6ce5602a

How parameters map to the AWS user interface

Following is a sample screenshot from AWS MediaPackage which shows where input parameters needed for the key derivation algorithm are configured.

At the time of writing the documentation, SPEKE v2 is only supported in the following two cases:

  • Live DASH-ISO

  • Live CMAF Apple HLS

Live DASH

Sample encryption settings used in AWS:

Configuring AWS - screenshot
Figure 1. AWS user intercace - screenshot

Based on above settings the following values are to be used for the key derivation algorithm.

  • Tenant ID: customer’s tenant Id.

  • Content ID: “test_content“

  • Protection Scheme: “cenc“ (DASH-ISO packaging type only uses CENC)

  • Content Key Period Index: “0“ (Since key rotation is disabled)

  • Intended Track Type for key one: “VIDEO“

  • Intended Track Type for key two: “AUDIO“

Note: AWS only supports two keys in the request currently. All video streams will use the first key and all audio streams will use the second key. Therefore, the intended track type will always consist of above values.

Live CMAF Apple HLS

For CMAF packaging all of the above parameters will be the same, except for the protection scheme which will be “cbcs“:

  • Tenant ID: customer’s tenant Id.

  • Content ID: “test_content“

  • Protection Scheme: “cbcs“ (CMAF packaging only uses CBCS)

  • Content Key Period Index: “0“ (Since key rotation is disabled)

  • Intended Track Type for key one: “VIDEO“

  • Intended Track Type for key two: “AUDIO“

Packaging with key rotation

When key rotation is enabled AWS MediaPackage will request new keys on regular intervals. With each request Content Key Period Index will increase by 1 (starts at "0").

For example, if the key rotation interval is set to 3600 (in seconds - equals 1 hour), AWS MediaPackage sends a new request every hour.

Therefore, even with key rotation enabled, it is easy to use the key derivation algorithm to predict key IDs.