Table of Contents

Using CPIX

Axinom Key Service uses CPIX for keys delivery. The request is using a CPIX document, the response contains the requested keys.

It is required to use the 'v2' API version, on the URL endpoint (/SpekeV2) while adding the following HTTP header X-Speke-Version: 2.0.


The Axinom Key Service 'v2' API uses CPIX 2.3, whereas USP follows the CPIX 2.2 specification.

The main differences between CPIX 2.2 and 2.3 are the following:

  • Addition of the commonEncryptionScheme element with the CENC protection scheme value
  • Addition of the version element
  • Addition of a section on using the same content key with different encryption schemes
  • Clarification on the explicitIV element encoding

An example API call:


curl '' \
  -v \
  -H 'Authorization: Basic <Credentials>' \
  -H "Content-Type: application/xml" \
  -H "X-Speke-Version: 2.0" \
  --data "@request.cpix" \
  > response.cpix

An example request document, request.cpix in above call example:

<?xml version="1.0"?>
<cpix:CPIX xmlns:cpix="urn:dashif:org:cpix" xmlns:pskc="urn:ietf:params:xml:ns:keyprov:pskc" id="test_ch1">
    <cpix:ContentKey kid="af1ed63c-5784-460b-9e51-309dd47b7d9c"/>
    <cpix:DRMSystem systemId="94ce86fb-07ff-4f43-adb8-93d2fa968ca2" kid="af1ed63c-5784-460b-9e51-309dd47b7d9c"/>
    <cpix:DRMSystem systemId="9a04f079-9840-4286-ab92-e65be0885f95" kid="af1ed63c-5784-460b-9e51-309dd47b7d9c"/>
    <cpix:DRMSystem systemId="edef8ba9-79d6-4ace-a3c8-27dcd51d21ed" kid="af1ed63c-5784-460b-9e51-309dd47b7d9c"/>

An example response document, response.cpix in above call example:

<?xml version="1.0" encoding="utf-8"?>
    xmlns:pskc="urn:ietf:params:xml:ns:keyprov:pskc" id="test_ch1">
        <cpix:ContentKey kid="af1ed63c-5784-460b-9e51-309dd47b7d9c" explicitIV="4pr4zDatXEUNKlYCD42+dg==">
        <cpix:DRMSystem systemId="94ce86fb-07ff-4f43-adb8-93d2fa968ca2" kid="af1ed63c-5784-460b-9e51-309dd47b7d9c" />
        <cpix:DRMSystem systemId="9a04f079-9840-4286-ab92-e65be0885f95" kid="af1ed63c-5784-460b-9e51-309dd47b7d9c" />
        <cpix:DRMSystem systemId="edef8ba9-79d6-4ace-a3c8-27dcd51d21ed" kid="af1ed63c-5784-460b-9e51-309dd47b7d9c" />

The resulting CPIX document then can be used as outlined in Content Protection Information eXchange (CPIX), for instance:


mp4split -o wiseplay.ism \
  --mpd.cpix=response.cpix \


The following section outlines the pre-CPIX API.


An alternative to the CPIX-based API is the JSON-based Axinom Widevine CENC API, which follows Google's Widevine Key Server key exchange protocol.

The following script shows how to create a single Axinom DRM compatible USP server manifest, which can then be used to playback content protected by Widevine, PlayReady or FairPlay DRM.

The script covers the following steps:

  1. Prepare a signed key request using an example request template and your account credentials.
  2. Get the content key and other DRM signaling information Axinom Key Service's "WidevineProtectionInfo" endpoint. Parse the key response and extract the necessary data to be used as input for "mp4split".
  3. Create the server manifest, supporting Widevine, PlayReady and FairPlay DRMs.

Overall steps to quickly test integration:

  1. Meet the "prerequisites" mentioned in the beginning of the script.
  2. Run the script.
  3. Note down the KID.
  4. Log on to your Axinom Portal account and use the "DRM Video Playback" tools to generate a license token for the KID and choose a player for playback.


The provided example is intentionally simplistic. For a full overview of the capabilities, such as returning multiple keys for different tracks or having the the key service generate KIDs, please visit the "Widevine Common Encryption" documentation on Axinom Portal.

Server manifest creation script


# Prerequisites
# Install Python pcryptodome library for signing the key request:
#   - Install Python PIP package manager: "sudo apt install python3-pip"
#   - Install Python crypo library: "sudo pip install pycryptodome"

# Adapt the following fields in the script according to your Axinom account details:
#   - Set "signer" to your Key Service Widevine Provider Name
#   - Set "signing_key16" to your Key Service Widevine Signing Key (64-character hex string)
#   - Set "signing_iv16" to your Key Service Widevine Signing IV (32-character hex string)
#   - Set "key_service_url" to your Key Service "CENC" endpoint. It is prefilled with the
#             most commonly used value.


# Fixed KID to use for simple testing. Feel free to adjust or to generate a random one.
# See API doc for how to let key service generate it.

# Convert KID (UUID string) to the Content ID form used by this protocol, which is the
# base64 of the full KID string (including dashes).
content_id=$(echo -n $kid | base64)

# Initialize the sample request that asks for one key (for "SD" track), generated based
# on the provided Content ID, and asks for DRM signalling data for Widevine, PlayReady
# and FairPlay.

# Widevine CENC API requests must be signed - let's generate the signature.
key_request_sha1=$(echo -n $key_request | sha1sum | head -c 40)
key_request64=$(echo -n $key_request | base64 -w0)
signature64=$(python3 -c 'from Crypto.Cipher import AES;from Crypto.Util.Padding import pad;import base64,binascii;"'$signing_key16'"),AES.MODE_CBC,binascii.unhexlify("'$signing_iv16'"));padded_text=pad(binascii.unhexlify("'$key_request_sha1'"),16);signature=aes.encrypt(padded_text);print(base64.b64encode(signature).decode("utf-8"))')

# Now we can compose the final signed request.

# Send the request to the key service
http_response=`curl -s -X POST -H "Content-Type: application/json" -d $signed_request $key_service_url`

# Extract data from the key service response.
key_response=$(echo $http_response | python -c 'import json,jwt,sys,base64;obj=json.load(sys.stdin);print base64.b64decode(obj["response"])')

kid64=$(echo $key_response | python3 -c 'import json,sys;obj=json.load(sys.stdin);print(obj["tracks"][0]["key_id"])')
cek64=$(echo $key_response | python3 -c 'import json,sys;obj=json.load(sys.stdin);print(obj["tracks"][0]["key"])')
iv64=$(echo $key_response | python3 -c 'import json,sys;obj=json.load(sys.stdin);print(obj["tracks"][0]["iv"])')
widevine_pssh64=$(echo $key_response | python3 -c 'import json,sys;obj=json.load(sys.stdin);print(obj["tracks"][0]["pssh"][0]["data"])')
playready_pssh64=$(echo $key_response | python3 -c 'import json,sys;obj=json.load(sys.stdin);print(obj["tracks"][0]["pssh"][1]["data"])')
skd_uri=$(echo $key_response | python3 -c 'import json,sys;obj=json.load(sys.stdin);print(obj["tracks"][0]["skd_uri"])')

# Make necessary conversions.
kid16=$(echo $kid64 | base64 -d | hexdump -e '16/1 "%02x"')
cek16=$(echo $cek64 | base64 -d | hexdump -e '16/1 "%02x"')
iv16=$(echo $iv64 | base64 -d | hexdump -e '16/1 "%02x"')
kid_uuid=`echo -n $kid16 | python3 -c 'import base64,uuid,sys;kid=sys.stdin.readline();print(uuid.UUID(kid))'`

# Set USP server manifest options
options_widevine+=" --widevine.key=${kid16}:${cek16}"
options_widevine+=" --widevine.drm_specific_data=${widevine_pssh64}"

options_playready+=" --iss.key=${kid16}:${cek16}"
options_playready+=" --iss.drm_specific_data=${playready_pssh64}"

options_fairplay+=" --hls.key=:${cek16}"
options_fairplay+=" --hls.key_iv=${iv16}"
options_fairplay+=" --hls.license_server_url=${skd_uri}"
options_fairplay+=" --hls.playout=sample_aes_streamingkeydelivery"

mp4split_options="${options_widevine} ${options_playready} ${options_fairplay}"

echo "Running mp4split with the following options:"
echo $mp4split_options

# Create USP server manifest (NB! Adjust input/output to your needs)
mp4split -o video.ism ${mp4split_options} video.ismv video.isma

echo "The KID for the media is: " $kid_uuid