• How to use the internal method of AES-128 encryption to secure streams sent to Apple iOS devices (ModuleEncryptionHandlerCupertinoStreaming)

    This article provides step-by-step instructions for using the internal method to configure secure streaming of live and on-demand (VOD) streams to Apple iOS devices.

    Notes:
    • This module was removed from the Wowza Module Collection because it's only an example that shows how to encrypt a stream. Before you can use it in production, you must add extra functionality to the module to determine if players are allowed to receive decryption keys.

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

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

    Start by configuring a Wowza Streaming Engine application for streaming to the iPhone. See the Wowza Streaming Engine User's Guide (PDF) or our Tutorials.

    The following skeleton module example shows how to implement the internal method for delivering an AES-128 encryption key.
    package com.wowza.wms.example.module;
    
    import com.wowza.util.*;
    import com.wowza.wms.http.*;
    import com.wowza.wms.httpstreamer.cupertinostreaming.httpstreamer.*;
    import com.wowza.wms.module.*;
    import com.wowza.wms.application.*;
    
    public class ModuleEncryptionHandlerCupertinoStreaming extends ModuleBase
    {
    	public void onHTTPCupertinoEncryptionKeyRequest(HTTPStreamerSessionCupertino httpCupertinoStreamingSession, IHTTPRequest req, IHTTPResponse resp)
    	{
    		boolean isGood = true;
    		
    		String ipAddress = httpCupertinoStreamingSession.getIpAddress();
    		String queryStr = req.getQueryString();
    		String referrer = httpCupertinoStreamingSession.getReferrer();
    		String cookieStr = httpCupertinoStreamingSession.getCookieStr();
    		String userAgent = httpCupertinoStreamingSession.getUserAgent();
    		String sessionId = httpCupertinoStreamingSession.getSessionId();
    		
    		IApplicationInstance appInstance = httpCupertinoStreamingSession.getAppInstance();
    		String streamName = httpCupertinoStreamingSession.getStreamName();
    		
    		// reject encryption key requests that are not delivered over SSL
    		//if (!req.isSecure())
    		//	isGood = false;
    
    		getLogger().info("ModuleEncryptionHandlerCupertinoStreaming.onHTTPCupertinoEncryptionKeyRequest["+appInstance.getContextStr()+"/"+httpCupertinoStreamingSession.getStreamName()+"]: accept:"+isGood);
    		
    		if (!isGood)
    			httpCupertinoStreamingSession.rejectSession();
    	}
    
    	public void onHTTPCupertinoEncryptionKeyData(HTTPStreamerSessionCupertino httpCupertinoStreamingSession, IHTTPRequest req, IHTTPResponse resp, byte[] encKeyData)
    	{
    	// Look at the key data, change or output something else for the request via the resp object
    	}
    	
    	public void onHTTPCupertinoEncryptionKeyCreateLive(IApplicationInstance appInstance, String streamName, byte[] encKey)
    	{
    		String mySharedSecret = appInstance.getProperties().getPropertyStr("cupertinoEncryptionSharedSecret", "");
    		
    		String hashStr = mySharedSecret+":"+appInstance.getApplication().getName()+":"+appInstance.getName()+":"+streamName;
    
    		byte[] tmpBytes = MD5DigestUtils.generateHashBytes(hashStr);
    		if (tmpBytes != null)
    			System.arraycopy(tmpBytes, 0, encKey, 0, encKey.length);
    		
    		getLogger().info("ModuleEncryptionHandlerCupertinoStreaming.onHTTPCupertinoEncryptionKeyCreateLive["+appInstance.getContextStr()+"/"+streamName+"]: *"+BufferUtils.encodeHexString(encKey).substring(28));
    	}
    
    	public void onHTTPCupertinoEncryptionKeyCreateVOD(HTTPStreamerSessionCupertino httpCupertinoStreamingSession, byte[] encKey)
    	{
    		String ipAddress = httpCupertinoStreamingSession.getIpAddress();
    		String queryStr = httpCupertinoStreamingSession.getQueryStr();
    		String referrer = httpCupertinoStreamingSession.getReferrer();
    		String cookieStr = httpCupertinoStreamingSession.getCookieStr();
    		String userAgent = httpCupertinoStreamingSession.getUserAgent();
    		
    		IApplicationInstance appInstance = httpCupertinoStreamingSession.getAppInstance();
    		String streamName = httpCupertinoStreamingSession.getStreamName();
    		String sessionId = httpCupertinoStreamingSession.getSessionId();
    
    		String mySharedSecret = appInstance.getProperties().getPropertyStr("cupertinoEncryptionSharedSecret", "");
    		
    		String hashStr = mySharedSecret+":"+(httpCupertinoStreamingSession.isHTTPOrigin() ? "" : sessionId+":")+appInstance.getApplication().getName()+":"+appInstance.getName()+":"+httpCupertinoStreamingSession.getStreamName();
    				
    		byte[] tmpBytes = MD5DigestUtils.generateHashBytes(hashStr);
    		if (tmpBytes != null)
    			System.arraycopy(tmpBytes, 0, encKey, 0, encKey.length);
    		
    		getLogger().info("ModuleEncryptionHandlerCupertinoStreaming.onHTTPCupertinoEncryptionKeyCreateVOD["+appInstance.getContextStr()+"/"+httpCupertinoStreamingSession.getStreamName()+"]: *"+BufferUtils.encodeHexString(encKey).substring(28));
    	}
    
    }
    The following methods control the encryption process:

    • The onHTTPCupertinoEncryptionKeyRequest method controls access to the encryption key. This method is unfinished and requires additional code to protect the key. Use this method to add logic to check the query string, cookies, or other request values to control access to the encryption key.

    • The onHTTPCupertinoEncryptionKeyData method controls access to the encryption key data. This method is unfinished and requires additional code. Use this method to add logic to change the key data before it's used or to remove the key data and output custom information via the resp object pointer.

    • The onHTTPCupertinoEncryptionKeyCreateLive method is called for each live HLS playback session. Use this method to control the key that's used for encryption. This method creates a unique MD5 hash that's based on a shared secret and the stream's context path. This guarantees that your encryption keys are unique.

    • The onHTTPCupertinoEncryptionKeyCreateVOD method is called for each on-demand HLS playback session. This method generates a unique encryption key per session (based on a shared secret), the session ID, and the stream's context path.

    Note: This example only encrypts HLS streams. If you want finer control over the session, you must use the Wowza IDE to complete and compile this module.

    Configuration

    1. Open the [install-dir]/conf/[application]/Application.xml file in a text editor and add the following module definition as the last entry in the <Modules> list:
      <Module>
      	<Name>ModuleEncryptionHandlerCupertinoStreaming</Name>
      	<Description>ModuleEncryptionHandlerCupertinoStreaming</Description>
      	<Class>com.wowza.wms.example.module.ModuleEncryptionHandlerCupertinoStreaming</Class>
      </Module>
    2. Add the following properties to the application-level <Properties> container at the bottom of the [install-dir]/conf/[application]/Application.xml file (be sure to get the correct <Properties> container as there are several in the file):
      <Property>
      	<Name>cupertinoEncryptionBaseURL</Name>
      	<Value>http://[wowza-ip-address]:1935</Value>
      </Property>
      <Property>
      	<Name>cupertinoEncryptionSharedSecret</Name>
      	<Value>[enckeysharedsecret]</Value>
      </Property>
      <Property>
      	<Name>cupertinoEncryptionLiveRepeaterSharedSecret</Name>
      	<Value>[mysharedsecret]</Value>
      </Property>

      If you use this method with Wowza nDVR


      The properties to add are slightly different. Add the following properties to the DVR <Properties> container in the [install-dir]/conf/[application]/Application.xml file (be sure to get the correct <Properties> container as there are several in the file):
      <Property>
      	<Name>cupertinoEncryptionBaseURL</Name>
      	<Value>http://[wowza-ip-address]:1935</Value>
      </Property>
      <Property>
      	<Name>cupertinoEncryptionSharedSecret</Name>
      	<Value>[enckeysharedsecret]</Value>
      </Property>
      The cupertinoEncryptionBaseURL property value is the base URL that's used to fetch the encryption key. If you have configured SSL, be sure to omit the port number in the URL (:1935), change the URL prefix from http:// to https://, and use the domain name instead of the IP address to address the Wowza Streaming Engine.

      The cupertinoEncryptionSharedSecret property value is the shared secret that's used in the hash algorithm to generate unique encryption keys. The sample code above illustrates how this value is used. An example shared secret is 1ghtY6D3Wgn.

      The cupertinoEncryptionLiveRepeaterSharedSecret property value is the shared secret that's used when the encryption key is sent to an edge server in a live stream repeater (origin/edge) configuration. When configuring a live stream repeater edge to deliver a live stream, be sure to set cupertinoEncryptionLiveRepeaterSharedSecret to the same value. An example value is 3Er5sWl09xfE).

    3. Add the cupertinoEnableOnEncKey property to the <HTTPStreamer>/<Properties> container in the [install-dir]/conf/[application]/Application.xml file:
      <Property>
      	<Name>cupertinoEnableOnEncKey</Name>
      	<Value>true</Value>
      	<Type>Boolean</Type>
      </Property>
      This property turns on the internal key delivery handler.

    Validating encryption

    To validate encryption for live streaming, you can look for log messages in the Streaming Engine access log.

    When a live stream is published, you'll see log messages similar to the following in the Streaming Engine access log:

    LiveStreamPacketizerCupertino.init[live/_definst_/myStream]: Encrypt Cupertino stream: key: *24bc url: http://192.168.1.120:1935/live/_definst_/myStream/key{sessionid}.m3u8key

    When an on-demand streaming playback session starts, you'll see a log message similar to the following in the Streaming Engine access log:

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

    Testing encryption

    To test AES encryption, see How to test AES encryption for Apple HLS streams

    Configuration notes

    For delivery of the encryption key, It's best to configure a <HostPort> in the [install-dir]/conf/VHost.xml file that uses SSL encryption. This protects the encryption key from being intercepted in transit. See "Configuring SSL and RTMPS" in the Wowza Streaming Engine User's Guide for more information about SSL configuration. If you configure a port that uses SSL encryption, be sure to uncomment the if (!req.isSecure()) check in onHTTPCupertinoEncryptionKeyRequest to ensure that the SSL port is used to deliver the key.

    For nDVR in origin-edge mode, the origin and edge servers use a common shared secret string to encrypt data sent between instances. You can use the dvrEncryptionSharedSecret or liveRepeaterEncryptionSharedSecret properties to customize the shared secret value that's used. For more information, see the How to do advanced configuration for Wowza nDVR.

    Wowza media server software and all components, including modules, source code, and other related items offered on this page, are copyrighted (c) 2006-2016 by Wowza Media Systems, LLC, all rights reserved, and are licensed pursuant to the Wowza Media Software End User License Agreement.

    Originally Published: 10-02-2010.
    Updated: For Wowza Streaming Engine 4.0.6 on 08-11-2014.

    If you're having problems or want to discuss this article, post in our forum.