• How to secure Apple HTTP Live Streaming (AES-128 - external method)

    Step-by-step instructions for configuring secure streaming of a live or video on demand stream to Apple iOS devices using the external method.

    Note: iPhone/iPod touch OS Version 3.0 is required.


    The protocol and security specifics about iOS devices are covered in detail in the Internet Engineering Task Force draft-pantos-http-live-streaming-05 specification.

    With the external method of AES-128 encryption, encryption keys are delivered by using an external URL. Wowza Media Server. There is also an internal method in which all media chunks and encryption keys are delivered using Wowza Media Server. For more information about how to use the internal method, see How to secure Apple HTTP Live Streaming (AES-128, internal method).

    1. Start by configuring an Wowza Media Server application for streaming by following one of the video on demand or live streaming tutorials. You will also need access to a web server.

    2. Configure an AES-128 key for encrypting the stream. We do this using the genkey utility that ships with Wowza Media Server in the [install-dir]/bin folder:

      1. Open a command shell and change directory to [install-dir]/bin.

      2. Enter the following command:

        Windows:
        Code:
        genkey.bat iphone [stream-name] [key-url]
        Linux:
        Code:
        ./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 the iOS device will use to receive the key data (see the specifics on the [key-url] below). This command will generate the file [stream-name].key. An example command line is:
        Code:
        genkey.bat iphone myStream https://mycompany.com/myStream.php
        The output should look this:
        Code:
        key: B9A954F22ACD093BDEB80622EC7155BB
        url: https://mycompany.com/myStream.php
        
        Key file generated: myStream.key
        Copy file to: [install-dir]/keys
      3. Copy the file generated by the command-line tool to the [install-dir]/keys folder. See below for details about how to set up an application-specific keys folder.


    3. Start Wowza Media Server and send the live stream to the server. When the live stream is published to the server, you will see the following log messages:
      Code:
      Encrypt iPhone stream: key: *[last-8-characters-of-key]
      Encrypt iPhone stream: url: [key-url]
      This indicates that the stream is being encrypted properly.


    The [key-url] is the URL that the devices will use to fetch the key to decrypt the stream. This is where you can control who can view your stream. 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. We suggest that this URL be protected by HTTPS so that the key is not sent over the Internet in the clear. The key information must be sent as a packed array of 16 octets in binary format with the following header information:
    Code:
    Content-Type: binary/octet-stream
    Pragma: no-cache
    The following examples show how to send the key data using popular web application technologies such as ASP.NET, JSP, and PHP. The key being sent in these examples is DE51A7254739C0EDF1DCE13BBB308FF0. Substitute the key generated above for this value:

    ASP.NET example
    Code:
    <%@ 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
    Code:
    <%@ 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
    Code:
    <?php
    
    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
    }
    
    ?>
    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 can access the content, you can block them from receiving the decryption key by setting the isValid value to false.

    These examples are provided as-is with no expressed warranty. Feel free to modify or distribute them without restriction.

    [install-dir]
    Windows (default): C:\Program Files\Wowza Media Systems\Wowza Media Server [version]
    Mac OS X: /Library/WowzaMediaServer
    Linux: /usr/local/WowzaMediaServer

    Note: 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]/Application.xml in a text editor and change the Streams/KeyDir property value. If you set this value to an empty value (zero length), then it defaults to:
    Code:
    [install-dir]/applications/[application]/keys/[appInstance]

    Note: In Wowza Media Server 2 (preview 7) or later, any query parameters passed as part of the playlist.m3u8 URL as well as the wowza session id will be passed to the key URL. For example, if you play a stream using the URL:
    Code:
    http://[wowza-ip-address]:1935/live/myStream/playlist.m3u8?userid=12345
    And the key URL is defined as:
    Code:
    https://mycompany.com/myStream.php?keyinfo=securekey
    Then the URL sent to myStream.php will include the following query parameters:
    Code:
    https://mycompany.com/myStream.php?keyinfo=securekey&userid=12345&wowzasessionid=345234




    Comments 61 Comments
    1. indikaf -
      Hello,

      Media Server Version: 2.1.1 (free)
      IOS version: 4.3.5 (8L1)

      I have configured wowza server using this (http://www.wowza.com/downloads/tutor...pplication.xml) configurations and the streaming is fine on iphone. Then I have followed the above instruction to make it secure using a key-url. However streaming is working even if I have set to send a wrong key through the key-url. That means the server is not requesting the encrypted key from the key url at all. My key-url is an aspx page and using http (non secure) at the moment.

      Can some tell me what is wrong please.

      Many thanks,

      Indika
    1. rrlanham -
      Indika,

      You can use HTTProviders and HTTPUTils. I.e. call and process response from HTTProvider on one Wowza server with HTTPUtils on another

      Richard
    1. indikaf -
      Hi Richard,

      Thanks for your response.

      I am very new to the wowza community. I like to use this "AES-128 - external method" as described here. Is this "HTTProviders and HTTPUTils" a different approach or a part of external method?

      Further, my key-url is in a windows server (not wowza) and an .aspx page responding to the request using your c# example. I am purposely sending a wrong key but the streaming does not stop. In fact, wowza does not request the key from key-url.

      Many thanks,

      Indika
    1. rrlanham -
      Indika,

      AES is for encrypting Cupertino streams. Nothing to do with Wowza server to Wowza server communcations.

      Here are some examples of HTTProviders:
      http://www.wowza.com/forums/content.php?30

      HTTPUtils is an API included in Wowza to load and process HTTP resources, such as HTTProviders. See the Wowza ServerSide API for more info.
      http://www.wowza.com/forums/content....-documentation

      Richard
    1. indikaf -
      Hi Richard,

      Thanks for your response again.

      I thought once the generated key copied to the correct folder ([install-dir]/keys ) wowza would pick that one up and communicate with key-url for the encrypted key. Basically I thought communicating functionality was built in to the server.


      Many thanks,

      Indika
    1. indikaf -
      Hi Richard,

      I have done exactly what it says on this page. Iphone can still play the stream even if the key-url is set to send a wrong key. This is very urgent now. Can you please help.

      Cheers !
    1. rrlanham -
      Did you copy the generated keys to [wowza-install-dir]/keys folder (step 3)?

      Zip up the conf *and* logs *and* keys folders, and send them to support@wowza.com.

      Include a link to this thread for reference.

      Richard
    1. indikaf -
      OK after hours of digging I found that the problem is my stream/video file name.

      I am doing vod streaming and we have loads of mp4 videos for streaming. Files are in sub folders. Following is an example of a iphone url.

      http://[wowza-server]:1935/[application]/mp4:[sub_folder_name]%5c[video_filename.mp4]/playlist.m3u8
      Where %5c is for the "\" as it is not allowed on safari

      Streaming works without encryption.

      Now my question are:
      1. how to use this method with video files in subfolders?
      2. Do I have to generate keys for each and every stream? This is not going to be practical

      Your quick response is highly appreciated.
    1. rrlanham -
      I think you can just generate one and copy it to different stream names.

      When files are in sub-folders, the path is part of the stream name. So you should include full name when generating key.

      Richard
    1. indikaf -
      Hi Richard,

      Full name cannot be included when generating key as it has "\" which is not allowed in windows filenames. genkey gives an error. However, if I generate a key with just the filename and drop it in [install-dir]\keys\[subfolder]\filename.mp4.key it works.

      So in theory wowza expects the same sub-folder structure inside the keys folder. That is sorted....

      Next, As videos are uploaded by our clients it is not practical to copy/generate the key file for each uploaded video. Is there a way to have a single key for the whole application (i.e. an application level key rather than stream level keys) ??

      Your quick response is highly appreciated.

      Indika
    1. rrlanham -
      Indika,

      Take a look at IMediaWriterActionNotify interface:

      http://www.wowza.com/forums/content....write-listener

      You can use onWriteComplete event handler to write a key file after each recording. You could use a template key file and just make a copy using java.io.File API

      Richard
    1. indikaf -
      Hi Richard,

      Thanks for the response...

      What do you mean by "after each recording" ? We are not recording but doing VOD streaming.

      Will this event be fired in VOD streaming as well? If yes, then when does it fire?

      Indika
    1. rrlanham -
      Indika,

      I see, my mistake. No, that event will not fire. If you are providing an upload mechanism, a php or asp page, you can do the same there: copy a template key to new file with the correct name.

      Richard
    1. indikaf -
      Thanks...
    1. jakehilton -
      Indika,
      You can also look at this discussion I started here: http://www.wowza.com/forums/showthre...eam&highlight=

      It discusses a way to secure streams down without generating a key for each one. Start on page 3.

      Jake
    1. aynajus -
      HiI used AES-128 - external method. and It work.When key invalid, player alert "You are not authorized to open this file."Can I change the word for alert to user?Thank , Nui
    1. charlie -
      I do not think there is a way to change this message.

      Charlie
    1. CostaDirectOne -
      Hello,

      I followed this example: iPhone/iPad iOS - (AES-128-external-method) and it worked great for the VOD application, but I'm having trouble getting it to work for a liverepeater-edge application

      Application.xml - configuration
      Code:
      <Root>
      	<Application>
      		<!-- Uncomment to set application level timeout values
      		<ApplicationTimeout>60000</ApplicationTimeout>
      		<PingTimeout>12000</PingTimeout>
      		<ValidationFrequency>8000</ValidationFrequency>
      		<MaximumPendingWriteBytes>0</MaximumPendingWriteBytes>
      		<MaximumSetBufferTime>60000</MaximumSetBufferTime>
      		<MaximumStorageDirDepth>25</MaximumStorageDirDepth>
      		-->
      		<Connections>
      			<AutoAccept>true</AutoAccept>
      			<AllowDomains></AllowDomains>
      		</Connections>
      		<!--
      			StorageDir path variables
      			
      			${com.wowza.wms.AppHome} - Application home directory
      			${com.wowza.wms.ConfigHome} - Configuration home directory
      			${com.wowza.wms.context.VHost} - Virtual host name
      			${com.wowza.wms.context.VHostConfigHome} - Virtual host config directory
      			${com.wowza.wms.context.Application} - Application name
      			${com.wowza.wms.context.ApplicationInstance} - Application instance name
      			
      		-->
      		<Streams>
      			<StreamType>liverepeater-edge</StreamType>
      			<StorageDir>${com.wowza.wms.context.VHostConfigHome}/content</StorageDir>
      			<KeyDir>${com.wowza.wms.context.VHostConfigHome}/keys/${com.wowza.wms.context.Application}/${com.wowza.wms.context.ApplicationInstance}</KeyDir>
      			<!-- LiveStreamPacketizers (separate with commas): cupertinostreamingpacketizer, smoothstreamingpacketizer, cupertinostreamingrepeater, smoothstreamingrepeater -->
      			<LiveStreamPacketizers>cupertinostreamingrepeater</LiveStreamPacketizers>
      			<!-- Properties defined here will override any properties defined in conf/Streams.xml for any streams types loaded by this application -->
      			<Properties>
      			</Properties>
      		</Streams>
      		<!-- HTTPStreamers (separate with commas): cupertinostreaming, smoothstreaming -->
      		<HTTPStreamers>cupertinostreaming</HTTPStreamers>
      		<SharedObjects>
      			<StorageDir></StorageDir>
      		</SharedObjects>
      		<Client>
      			<IdleFrequency>-1</IdleFrequency>
      			<Access>
      				<StreamReadAccess>*</StreamReadAccess>
      				<StreamWriteAccess>*</StreamWriteAccess>
      				<StreamAudioSampleAccess></StreamAudioSampleAccess>
      				<StreamVideoSampleAccess></StreamVideoSampleAccess>
      				<SharedObjectReadAccess>*</SharedObjectReadAccess>
      				<SharedObjectWriteAccess>*</SharedObjectWriteAccess>
      			</Access>
      		</Client>
      		<RTP>
      			<!-- RTP/Authentication/[type]Methods defined in Authentication.xml. Default setup includes; none, basic, digest -->
      			<Authentication>
      				<PublishMethod>none</PublishMethod>
      				<PlayMethod>digest</PlayMethod>
      			</Authentication>
      			<!-- RTP/AVSyncMethod. Valid values are: senderreport, systemclock, rtptimecode -->
      			<AVSyncMethod>senderreport</AVSyncMethod>
      			<MaxRTCPWaitTime>12000</MaxRTCPWaitTime>
      			<IdleFrequency>75</IdleFrequency>
      			<RTSPSessionTimeout>90000</RTSPSessionTimeout>
      			<RTSPMaximumPendingWriteBytes>0</RTSPMaximumPendingWriteBytes>
      			<RTSPBindIpAddress>[edge-address]</RTSPBindIpAddress>
      			<RTSPConnectionIpAddress>[edge-address]</RTSPConnectionIpAddress>
      			<RTSPOriginIpAddress>[edge-address]</RTSPOriginIpAddress>
      			<IncomingDatagramPortRanges>*</IncomingDatagramPortRanges>
      			<!-- Properties defined here will override any properties defined in conf/RTP.xml for any depacketizers loaded by this application -->
      			<Properties>
      			</Properties>
      		</RTP>
      		<MediaCaster>
      			<!-- Properties defined here will override any properties defined in conf/MediaCasters.xml for any MediaCasters loaded by this applications -->
      			<Properties>
      			</Properties>
      		</MediaCaster>
      		<MediaReader>
      			<!-- Properties defined here will override any properties defined in conf/MediaReaders.xml for any MediaReaders loaded by this applications -->
      			<Properties>
      			</Properties>
      		</MediaReader>
      		<MediaWriter>
      			<!-- Properties defined here will override any properties defined in conf/MediaWriter.xml for any MediaWriter loaded by this applications -->
      			<Properties>
      			</Properties>
      		</MediaWriter>
      		<LiveStreamPacketizer>
      			<!-- Properties defined here will override any properties defined in conf/LiveStreamPacketizers.xml for any LiveStreamPacketizers loaded by this applications -->
      			<Properties>
      				<Property>
      					<Name>forceH264BaselineProfile</Name>
      					<Value>true</Value>
      					<Type>Boolean</Type>
      				</Property>
      			</Properties>
      		</LiveStreamPacketizer>
      		<HTTPStreamer>
      			<!-- Properties defined here will override any properties defined in conf/HTTPStreamers.xml for any HTTPStreamer loaded by this applications -->
      			<Properties>
      			</Properties>
      		</HTTPStreamer>
      		<Repeater>
      			<OriginURL>rtmp://[origin-server-address]/[application-name]</OriginURL>
      			<QueryString><![CDATA[]]></QueryString>
      		</Repeater>
      		<Modules>
      			<Module>
      				<Name>base</Name>
      				<Description>Base</Description>
      				<Class>com.wowza.wms.module.ModuleCore</Class>
      			</Module>
      			<Module>
      				<Name>properties</Name>
      				<Description>Properties</Description>
      				<Class>com.wowza.wms.module.ModuleProperties</Class>
      			</Module>
      			<Module>
      				<Name>logging</Name>
      				<Description>Client Logging</Description>
      				<Class>com.wowza.wms.module.ModuleClientLogging</Class>
      			</Module>
      			<Module>
      				<Name>flvplayback</Name>
      				<Description>FLVPlayback</Description>
      				<Class>com.wowza.wms.module.ModuleFLVPlayback</Class>
      			</Module>
      		</Modules>
      		<!-- Properties defined here will be added to the IApplication.getProperties() and IApplicationInstance.getProperties() collections -->
      		<Properties>
      		</Properties>
      	</Application>
      </Root>
      the stream works using http://[repeater-address]:1935/[application-name]/[application-instance-name]/[stream-name]/playlist.m3u8

      I have the key named [stream-name].key in the /keys/[application-name]/[application-instance-name]/ directory and the url to the [stream-name].php which supplies the key is active and working (just like it worked for the VOD app), my problems is that the stream is not encrypted and will play with a valid or invalid key in the [stream-name].php file.

      One thing i noticed was that if I change the stream (there are multiple streams running on the origin on multiple application-instances) Wowza creates the key/[application-name]/[application-instance-name]/ directory looking for they key.

      Is this possible or must I update the Origin Application.xml conf file to match?
    1. rrlanham -
      What is [stream-name] ? Is it a simple name like "myStream", and you have the rtmp address of the origin in /originURL?

      If so, try using a .stream file instead. Create a text file named origin.stream in the content folder:

      /content/origin.stream, with contents:
      rtmp://[wowza-address]:1935/liveorigin/_definst_/myStream

      Now use stream name "origin.stream"

      Richard