Secure Apple HLS with AES-128 external encryption in Wowza Streaming Engine

This article provides instructions on using the external method of AES-128 encryption in Wowza Streaming Engine™ media server software to configure secure streaming of a live or video-on-demand (VOD) stream to HLS compatible devices that support AES-128 encryption. When you use the external method of AES-128 encryption, encryption keys are delivered by using an external URL.

Note: The protocol and security specifics for iOS devices are covered in detail in the Internet Engineering Task Force draft-pantos-hls-rfc8216bis-15.

Configuration


  1. Start by configuring a Wowza Streaming Engine application for live or VOD streaming. You will also need access to a web server.
     
  2. Create a key file in the [install-dir]/keys folder for each stream you want to encrypt. The key file is a text file with the name of the stream you want to protect and a .key extension. For example, to encrypt a live stream named myStream, create a key file in the [install-dir]/keys folder named myStream.key with content similar to the following:
     
    cupertinostreaming-aes128-key: DE51A7254739C0EDF1DCE13BBB308FF0
    cupertinostreaming-aes128-url: http:/example.com/security.aspx
    • cupertinostreaming-aes128-key is a 16-byte key that will match the key used to decrypt the stream. You will want to replace this key with one of your own.
    • cupertinostreaming-aes128-url is the URL that devices will use to fetch the key to decrypt the stream, as shown in the examples below. You can control who can view your stream by managing access to this URL.

    You must have a .key file for each stream that you want to protect. Each of these files may use the same 16-byte key and the same URL to return that key to devices, or you might have a different key and different URL for each stream.

    Note: You can also use the genkey utility to generate key files. A key file with a different key is generated each time you run this utility. You can integrate this utility into an automated workflow to create many .key files for a video library. For information, see Using genkey.

The key must be returned to a device as a packed array of 16 octets in binary format with the following header information:

Content-Type: binary/octet-stream
Pragma: no-cache

Examples


The following examples show how to use popular web application technologies such as ASP.NET, JSP, and PHP to send the key data. Each of these examples includes a Boolean isValid value that defaults to true. You can modify these examples to provide your own security tests to validate that the user can access the content. If the user should not be allowed to access the content, you can block them from receiving the decryption key by setting the isValid value to false.

If the request for this key returns a status of 403, then the device cannot decrypt and play the stream. If the key is returned, then the stream will be decrypted and played. Require HTTPS access to this key so that it is not sent over an unsecured connection on the Internet.

The key being sent in these examples is DE51A7254739C0EDF1DCE13BBB308FF0. Substitute this value with the 16-byte key set in the .key file in the Configuration section.

Note: These examples are provided as-is with no expressed warranty. You can modify or distribute them without restriction.

ASP.NET example

<%@ Page Language="C#" %>
<%

    Boolean isValid = true;
    if (!isValid)
    {
        Response.Status = "403 Forbidden";
    }
    else
    {
        Response.AddHeader("Content-Type", "binary/octet-stream");
        Response.AddHeader("Pragma", "nocache");

        String keyStr = "DE51A7254739C0EDF1DCE13BBB308FF0";

        int len = keyStr.Length/2;
        byte[] keyBuffer = new byte[len];

        for (int i=0;i<len;i++)
            keyBuffer[i] = Convert.ToByte(keyStr.Substring(i*2, 2), 16);

        Response.BinaryWrite(keyBuffer);
        Response.Flush();
        Response.End();
    }

%>

JSP example

<%@ page import="java.util.*,java.io.*" %>
<%

boolean isValid = true;
if (!isValid)
{
response.setStatus( 403 ); 
}
else
{
response.setHeader("Content-Type", "binary/octet-stream");
response.setHeader("Pragma", "no-cache");

String keyStr = "DE51A7254739C0EDF1DCE13BBB308FF0";

int len = keyStr.length()/2;
byte[] keyBuffer = new byte[len];

for (int i=0;i<len;i++)
keyBuffer[i] = (byte)Integer.parseInt(keyStr.substring(i*2, (i*2)+2), 16);

OutputStream outs = response.getOutputStream();
outs.write(keyBuffer);
outs.flush();
}

%>

PHP example

<?php

// Check if function exists (php5.4+ includes this method)
if(!function_exists("hex2bin")){
function hex2bin($h)
{
if (!is_string($h))
return null;
$r = '';
for ($a=0;$a<strlen($h);$a+=2)
{
$r .= chr(hexdec($h{$a}.$h{($a+1)}));
}
return $r;
}
}

$isValid = true;
if (! $isValid)
{
header('HTTP/1.0 403 Forbidden');
}
else
{
header('Content-Type: binary/octet-stream');
header('Pragma: no-cache');


echo hex2bin('DE51A7254739C0EDF1DCE13BBB308FF0');

exit(); // this is needed to ensure cr/lf is not added to output
}

?>

To validate encryption for a live stream, you will see log messages in the Wowza Streaming Engine access log similar to the following when the stream is published:

LiveStreamPacketizerCupertino.init[myApplication/_definst_/myStream]: Encrypt Cupertino stream: key: *55BB url: http://192.168.1.120:1935/myApplication/_definst_/myStream/key{sessionid}.m3u8key

To validate encryption for video on demand streaming, you will see a log message similar to the following example in the Wowza Streaming Engine access log when an HLS (cupertino) playback session starts:

HTTPStreamerCupertinoIndexFile.init[myApplication/_definst_/sample.mp4]: Encrypt Cupertino stream: key: *763a url: http://192.168.1.120:1935/myApplication/_definst_/mp4:sample.mp4/key{bitrate}{sessionid}.m3u8key

Using genkey


Wowza Streaming Engine includes the genkey utility that you can use to generate key files.
 
  1. Open a command shell and change to the directory [install-dir]/bin.
     
  2. Enter the following command:

    Windows:
    genkey.bat iphone [stream-name] [key-url]
    Linux:
    ./genkey.sh iphone [stream-name] [key-url]

    Where [stream-name] is the name of the stream that will be encrypted and [key-url] is the URL that an iOS device will use to receive the key data (see specifics about the [key-url] below). This command will generate the file [stream-name].key. An example command line is:

    genkey.bat iphone myStream https://example.com/myStream.php

    The output should look this:

    key: B9A954F22ACD093BDEB80622EC7155BB
    url: https://example.com/myStream.php
    
    Key file generated: myStream.key
    Copy file to: [install-dir]/keys
  3. Copy the file generated in the previous step. You will find this file in the [install-dir]/bin folder. Copy it to the [install-dir]/keys folder. This will protect a stream with that name in any application. See below for details about how to set up an application-specific keys folder. 

By default, all applications are configured to use the same keys folder, which can lead to stream name conflicts. To configure a keys folder on a per-application basis, open [install-dir]/conf/[application-name]/Application.xml in a text editor and make the Streams/KeyDir property value empty.

<KeyDir></KeyDir>

When KeyDir is empty, Wowza Streaming Engine looks in [install-dir]/applications/[application-name]/keys/[appInstance] for your .key files. You can also use the StorageDir path variables shown in Application.xml to construct other locations.

Query parameters passed as part of the playlist.m3u8 URL (as well as the wowzasessionid) will be passed to the key URL. For example, if you play a stream using the URL

http://[wowza-ip-address]:1935/myApplication/myStream/playlist.m3u8?userid=12345

and the key URL is defined as the URL

https://example.com/myStream.php?keyinfo=securekey

then the URL sent to myStream.php will include the following query parameters:

https://example.com/myStream.php?keyinfo=securekey&userid=12345&wowzasessionid=345234