Content key API

**THIS DOCUMENT IS FOR SETTING UP WIDEVINE WITH YOUR OWN TRANSCODING AND STREAMING SETUP.**

VdoCipher also provides complete transcoding, multi-screen DRM and CDN streaming
as a SAAS solution. If you use our SAAS service, you will have the following
setup pre-packaged.

To obtain your DRM API keys, please contact us.

Widevine setup requires two steps

  1. Encryption
  2. Licensing

Encryption API

Creating a content id
  • The content id is a number associated with a video content or live video stream. The format when passing to shaka packager has to be hex. When used with Encryption API, the format has to be base64. This is because shaka packager converts the hex input to base64.
  • In the licensing API, the content id needs to be passed in hex format
  • The minimum length of content id in bytes should be 16 bytes. This translates to 32 characters of hex bytes and 24 characters in base64. Using smaller content id size will result in HTTP 400 status code.
  • Re-using the same content id again will result in HTTP 400 status. The response body will contain readable error message but the encoding tool might not be able to output this error message.
  • For live-streams, it is recommended to save the keys and IVs in your database as well because the same content id can not be re-used and you might want to re-create the same live stream output multiple times while keeping the same content id.

Widevine encryption

Widevine encryption is done after transcoding and before sending the content on CDN. The CENC encryption is usually done with MPEG-DASH streaming protocol. To enable encryption, you need to create a unique content key for every content. You should not have separate content keys for each track. Internally, most encryption tool will automatically request multiple keys for each track. This content id is sent to the content key API which returns a unique encryption key to be used. Practically, you do not need to handle these steps yourself. There are softwares which can make this easier. Notable such tools are shaka packager and Bento4. Often, the trancoding tools themselves will have a widevine encryption step built as part of their pipeline.

List of content key API parameters required for encryption setup:

  1. AES Key
  2. AES IV
  3. Signer name
  4. Content key URL
Shaka packager for Live
packager \
  'in=udp://127.0.0.1:40000,stream=audio,init_segment=live_cam_audio.mp4,segment_template=live_cam_audio_$Number$.m4s' \
  'in=udp://127.0.0.1:40000,stream=video,init_segment=live_cam_video.mp4,segment_template=live_cam_video_$Number$.m4s' \
  --enable_widevine_encryption \
  --key_server_url https://license.uat.widevine.com/cenc/getcontentkey/_______ \
  --content_id _____________________ \
  --signer _______ \
  --aes_signing_key _____________________ \
  --aes_signing_iv ___________ \
  --mpd_output live_cam.mpd
Shaka packager for On-demand video
packager \
  in=h264_baseline_360p_600.mp4,stream=audio,output=audio.mp4 \
  in=h264_main_480p_1000.mp4,stream=video,output=h264_480p.mp4 \
  in=h264_main_720p_3000.mp4,stream=video,output=h264_720p.mp4 \
  in=h264_high_1080p_6000.mp4,stream=video,output=h264_1080p.mp4 \
  --enable_widevine_encryption \
  --key_server_url https://license.uat.widevine.com/cenc/getcontentkey/_______ \
  --content_id _____________________ \
  --signer _______ \
  --aes_signing_key _____________________ \
  --aes_signing_iv ___________ \
  --mpd_output h264.mpd

External links:

Fairplay Encryption API

Fairplay encryption is done after transcoding and before sending the content on CDN. The Sample-AES encryption is done with HLS streaming protocol. To enable encryption, you need to create a unique content key for every content. You should not have separate content keys for each track.

To obtain your widevine content key API parameters, please contact us. List of content key API parameters required for encryption setup:

Read this article on how to obtain Fairplay license from Apple

  • API secret key

Sample encryption code using Bento4 command: mp4hls

mp4hls --output-single-file --encryption-mode=SAMPLE-AES \
    --encryption-key=___________ \
    --encryption-iv-mode=fps \
    --encryption-key-format=com.apple.streamingkeydelivery \
    --encryption-key-uri=skd://______ video.mp4

Use the sample code on the right to obtain the keys and uri required in the above Bento4 command

External links:

const crypto = require('crypto');
const uuidv4 = require('uuid/v4');
const rp = require('request-promise-native');
const APIKEY = '_______________';
const provider_name = '______';

/**
 * @param {string|Buffer} key secret in utf8
 * @param {string} input utf8 input
 * @return {Buffer}
 */
const hmac = (key, input) => {
  const hm = crypto.createHmac('sha256', key);
  hm.update(input);
  return hm.digest();
};

// any random bytes generation process will work
// here we have uuid because it is synchronous and works well for examples
// this should be saved in the database corresponding to content
const contentId = uuidv4().replace(/-/g, '').toString('base64');
const keyRequestObj = {contentId};
const signingDate = (new Date()).toISOString().replace(/[-.:]/g, '');
const keyRequest = JSON.stringify(keyRequestObj);
const hash = hmac(hmac(APIKEY, signingDate), keyRequest).toString('base64');
const keyId = APIKEY.substr(0, 16);
const signature = `${keyId}:${signingDate}:${hash}`;

rp({
  url: `https://license.vdocipher.com/content-keys/fps/${provider_name}`,
  method: 'POST',
  json: true,
  body: {
    signature,
    keyRequest,
  },
}).then(console.log).catch((e) => console.error(e.message));
<?php

const API_KEY = "________________";
$provider_name = "__________";
$keyId = substr(API_KEY, 0, 16);

function getRandomB64($num_bytes=4) {
    return base64_encode(openssl_random_pseudo_bytes($num_bytes));
}

// any random bytes generation process will work
// this should be saved in the database corresponding to content
$contentId = getRandomB64(16);

echo bin2hex(base64_decode($contentId));

$signingDate = date("Ymd\THis\Z");
$keyRequest = json_encode([
    "contentId" => $contentId,
]);

$signedDate = hash_hmac("sha256", $signingDate, API_KEY, true);
$hash = base64_encode(hash_hmac("sha256", $keyRequest, $signedDate, true));

$signature = "$keyId:$signingDate:$hash";

$curl = curl_init();

curl_setopt_array($curl, array(
    CURLOPT_URL => "https://license.vdocipher.com/content-keys/fps/$provider_name",
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST => "POST",
    CURLOPT_POSTFIELDS => json_encode([
        "signature" => $signature,
        "keyRequest" => $keyRequest,
    ]),
    CURLOPT_HTTPHEADER => array(
        "Content-Type: application/json"
    ),
));

$response = curl_exec($curl);
$err = curl_error($curl);

curl_close($curl);

if ($err) {
    echo "cURL Error #:" . $err;
} else {
    echo $response;
}

Licensing API

Sample license API implemented in nodejs

Licensing API is used to create authentication token to allow playback on device. DRM enabled video players such as Exoplayer for Android or Dash player for web will have configuration option for providing license URL along with URL to your dash manifest. These players also have examples for how to setup widevine license URL. We recommended to generate unique license URL for every playback.

Note that this requires configuring your backend to allow either an Ajax API or generate the URL during page rendering. This is different from a typical video sharing site such as youtube where the embed code remains constant. A correct DRM setup must ensure that the data sent from the server should be limited to only the current setup.

The backend server will keep the API secret key. API secret key can be copied from your vdocipher dashboard in config section.

Use the language tab on top-right to choose your server language.

/**
 * PLACE THIS SNIPPET OUTSIDE THE REQUEST HANDLER
 */
const APIKEY = '_____________________________';

const crypto = require('crypto');
/**
 * @param {string|buffer} key secret in utf8
 * @param {string} input utf8 input
 * @return {string} Buffer
 */
const hmac = (key, input) => {
  const hm = crypto.createHmac('sha256', key);
  hm.update(input);
  return hm.digest();
};
/**
 * base64 a buffer into the url safe version
 * @param {buffer} input
 * @return {string}
 */
const urlSafeB64 = (input) => {
  return input.toString('base64').replace(/\//g, '_').replace(/\+/g, '-');
};
/**
 * INSIDE THE REQUEST HANDLER/CONTROLLER
 */
// obtain from database
const CONTENT_ID = '_________________';
const DRM_TYPE = '___'; // fps or wv
const timestamp = Math.floor(new Date().getTime() / 1000);
const contentAuthObj = {
  contentId: CONTENT_ID,
  expires: timestamp + 900,

};
const signingDate = (new Date()).toISOString().replace(/[-.:]/g, '');
const contentAuthStr = urlSafeB64(Buffer.from(JSON.stringify(contentAuthObj)));
const signedDate = hmac(APIKEY, signingDate);
const hash = urlSafeB64(hmac(signedDate, contentAuthStr));
const keyId = APIKEY.substr(0, 16);
const signature = `${keyId}:${signingDate}:${hash}`;
const LICENSE_URL = `https://license.vdocipher.com/auth/${DRM_TYPE}/` +
  urlSafeB64(Buffer.from(JSON.stringify({
    contentAuth: `${contentAuthStr}`,
    signature: `${signature}`,
  })));
console.log(LICENSE_URL);
/**
 * PLACE THIS SNIPPET OUTSIDE THE REQUEST HANDLER
 */
const API_KEY = '_____________________________';
$keyId = substr(API_KEY, 0, 16);

function urlSafeBase64Encode($data) {
    $base64 = base64_encode($data);
    return strtr($base64, '+/', '-_');
}
/**
 * INSIDE THE REQUEST HANDLER/CONTROLLER
 */
// obtained from database
$contentId = '_________________';
$drm_type = '___' // fps or wv

$signingDate = date('Ymd\THis\Z');
$timestamp = time();
$contentAuthStr = urlSafeBase64Encode(json_encode([
    "contentId" => $contentId,
    "expires" => $timestamp + 900,
]));

$signedDate = hash_hmac('sha256', $signingDate, API_KEY, true);
$hash = urlSafeBase64Encode(hash_hmac('sha256', $contentAuthStr, $signedDate, true));

$signature = "$keyId:$signingDate:$hash";
$licenseUrl = "https://license.vdocipher.com/auth/$drm_type/" . urlSafeBase64Encode(json_encode([
    'contentAuth' => $contentAuthStr, 'signature' => $signature,
    ]));
echo $licenseUrl;

Offline license

To request for an offline license, please make the alongside changes in your content auth string.

const contentAuthObj = {
  contentId: CONTENT_ID,
  expires: timestamp + 900,
  licenseRules: JSON.stringify({
    canPersist: true,
    rentalDuration: 15 * 24 * 3600, // 15 days
  })
};
$contentAuthStr = urlSafeBase64Encode(json_encode([
    "contentId" => $contentId,
    "expires" => $timestamp + 900,
    "licenseRules" => json_encode([
        "canPersist" => true,
        "rentalDuration" => 15 * 24 * 3600, // 15 days
    ])
]));

results matching ""

    No results matching ""