Encrypting for Fairplay
Suggested softwares​
Bento4​
Bento4 is another well-maintained open-source library which provides useful
media packaging and un-packaging utilities. It provides several binaries.
For our purpose, we will use the mp4hls command which can be used to generate
SAMPLE-AES output with Fairplay encryption.
Encryption steps​
Creating a content id​
Keep separate content-id for widevine and fairplay. Using the same content-id might cause playback problems. In your database, you should have two different fields: one for widevine-content-id and another for fairplay-content-id.
Content-ID is associated with a single encrypted output. One content-id can map to multiple encryption keys. The license URL is also generated using this content-id. We have an adaptive output with multiple tracks for video at different bitrates. Each of these tracks will have a different encryption key. To make things easier to handle and generate license URLs, there is a single content-id associated with a set of encryption keys. This way, you don't have to maintain different encryption keys for each track and the license server takes care of each track individually. Save this content-id in your database corresponding to the each video.
Sample code generating content-id of 16 bytes. Use a cryptographically secure function to generate this.
- Shell
- NodeJS
openssl rand -hex 16
const crypto = require('crypto');
crypto.randomBytes(16, (err, data) => {
const contentId = data.toString('hex');
console.log({contentId});
});
- In the licensing API, the content id needs to be passed in hex format
- The format when passing to shaka packager has to be
hex. When used with Encryption API, the format has to bebase64. This is because shaka packager converts the hex input to base64. - 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.
Fairplay Encryption​
Obtaining content keys​
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-id for every content.
Contact support@vdocipher.com to know the process for applying fairplay license.
List of content key API parameters required for encryption setup:
- API secret key
Use the sample code below to obtain the keys and uri required in the below Bento4 command
- NODE
- PHP
const crypto = require('crypto');
const request = require('request');
require('dotenv').config();
const APIKEY = process.env.License_API_Key;
const provider_name = process.env.widevine_signer_name;
// use random bytes generation here
// here this reads from argument which works well for examples
// this should be saved in the database corresponding to content
const contentId = Buffer.from(process.argv[2], 'hex').toString('base64');
/**
* @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();
};
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}`;
request(
{
url: `https://license.vdocipher.com/content-keys/fps/${provider_name}`,
method: 'POST',
json: true,
body: {signature, keyRequest},
},
(e, response, responseBody) => {
if (e) throw e;
if (response.statusCode === 200) {
console.log(responseBody);
} else {
console.log('HTTP Status: ', response.statusCode);
console.log('Response Body: ', responseBody);
}
},
);
const API_KEY = "___API_KEY____";
$provider_name = "___signer_name___";
$keyId = substr(API_KEY, 0, 16);
// any random bytes generation process will work
// this should be saved in the database corresponding to content
$contentId = "__contentId_in_hex_";
$signingDate = date("Ymd\THis\Z");
$keyRequest = json_encode([
"contentId" => base64_encode(hex2bin($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;
}
Response sample​
{
"key": "cf64bfe4f7dd9406212c56b3264ab07f",
"iv": "60d51d1f50eb469aedbf67eeff570861",
"uri": "skd://e29f989283a27e4df8da137d3c5cb2b9"
}
Running bento4 command​
Encryption command using Bento4 command: mp4hls
mp4hls --output-single-file --encryption-mode=SAMPLE-AES \
--encryption-key=${key}${iv} \
--encryption-iv-mode=fps \
--encryption-key-format=com.apple.streamingkeydelivery \
--encryption-key-uri=${uri} video.mp4
keyandivare passed concatenated as a single 64 character string