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

SPEKE 1.0 Override Functionality

Context

Amazon Web Services (AWS) Elemental services (MediaConvert and MediaPackage) utilize the SPEKE v1 protocol request keys for 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 the packaging.

Therefore, Axinom also 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 keyId extraction page.

Solution

Enabling the Key ID Override Functionality

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

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

Key ID Derivation Algorithm

When the key ID override is enabled, the KIDs can be predicted according to the Axinom SPEKE v1 key ID derivation algorithm and the examples presented below.

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
PartTowHash = 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 gives 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.