Secure MPEG-DASH streams using Common Encryption in Wowza Streaming Engine

Wowza Streaming Engine™ media server software can protect video-on-demand (VOD) and live MPEG-DASH streams using the Common Encryption (CENC) standard. Since CENC is DRM system-agnostic, it allows the same CENC-encrypted content to be decrypted and played by any client device that interfaces with a DRM system that can serve the associated CENC key information. CENC-encrypted MPEG-DASH streams also allow carriage of proprietary DRM system-specific data, which allows a client device to transact with the particular DRM system they are integrated with for key retrieval. Support for this proprietary DRM data requires some DRM-specific functionality to be added to the server internals for each DRM system. Wowza Streaming Engine supports on-the-fly MPEG-DASH CENC encryption for on demand and live content via the PlayReady and Widevine DRM systems. Support for other DRM systems is also being considered.

This article assume a familiarity with CENC and DRM technologies. Wowza Streaming Engine doesn't function as a CENC key server. It instead can be used to encrypt and deliver content on the fly.

Notes: 

Wowza DRM


Wowza Streaming Engine software includes Wowza DRM, a feature that enables direct integration with third-party CENC PlayReady DRM vendors. When using this option, you'll be using the PlayReady key server provided by the third-party vendor. Wowza DRM includes a direct integration with the third-party key server to acquire the encryption keys and license URLs needed to protect your content. For more detailed information, see About digital rights management and Wowza Streaming Engine.

On-the-fly DASH CENC encryption using key files


VOD and live content can be CENC-PlayReady or CENC-Widevine encrypted on the fly using key files. When using this option, you must provide a PlayReady or Widevine key server. Key files are text files that are located in the [install-dir]/keys folder that match the stream name of the stream you're playing with the addition of a .key extension. For example, if you want to protect the live stream myStream, you would create a key file with the path [install-dir]/keys/myStream.key.

To protect the on-demand stream sample.mp4, create a key file with the path [install-dir]/keys/sample.mp4.key.

The naming is similar for live and VOD MPEG-DASH adaptive bitrate streams referenced in SMIL files. For example, if you want to protect all of the streams in an adaptive group defined in the SMIL file sample.smil, create a key file with the path [install-dir]/keys/sample.smil.key. In this case, all of the streams in the adaptive group will be encrypted with the same key.

If the stream is at a path location such as [install-dir]/content/myfiles/sample.mp4, then the key file should be at a parallel location in the key folder: [install-dir]/keys/myfiles/sample.mp4.key.

The key file is a text file. For PlayReady DRM, it has the following (or similar) content:

mpegdashstreaming-cenc-key-id: F6005DCF-7F93-4B8E-85C7-F908840DA059
mpegdashstreaming-cenc-content-key: Tc2cQBPC/paTkftvaITCSQ==
mpegdashstreaming-cenc-algorithm: AESCTR
mpegdashstreaming-cenc-keyserver-playready: true
mpegdashstreaming-cenc-keyserver-playready-system-id: 9a04f079-9840-4286-ab92-e65be0885f95
mpegdashstreaming-cenc-keyserver-playready-license-url: http://myplayreadyserver.com/authenticate.aspx
mpegdashstreaming-cenc-keyserver-playready-checksum: vbxgstjfSQY=

For Widevine DRM, the key file has the following (or similar) content:

mpegdashstreaming-cenc-key-id: 0294B959-9D75-5DE2-BBF0-FDCA3FA5EAB7
mpegdashstreaming-cenc-content-key: O9ovQDRMfe9hQie5wPA+Jg==
mpegdashstreaming-cenc-algorithm: AESCTR
mpegdashstreaming-cenc-keyserver-widevine: true
mpegdashstreaming-cenc-keyserver-widevine-system-id: edef8ba9-79d6-4ace-a3c8-27dcd51d21ed
mpegdashstreaming-cenc-keyserver-widevine-pssh-data: CAESEAKUuVmddV3iu/D9yj+l6rcaDXdpZGV2aW5lX3Rlc3QiEGZrajNsamFTZGZhbGtyM2oqAlNEMgA=

For a combined PlayReady and Widevine DRM configuration, the key file has the following (or similar) content:

mpegdashstreaming-cenc-key-id: 0294B959-9D75-5DE2-BBF0-FDCA3FA5EAB7
mpegdashstreaming-cenc-content-key: O9ovQDRMfe9hQie5wPA+Jg==
mpegdashstreaming-cenc-algorithm: AESCTR
mpegdashstreaming-cenc-keyserver-widevine: true
mpegdashstreaming-cenc-keyserver-widevine-system-id: edef8ba9-79d6-4ace-a3c8-27dcd51d21ed
mpegdashstreaming-cenc-keyserver-widevine-pssh-data: CAESEAKUuVmddV3iu/D9yj+l6rcaDXdpZGV2aW5lX3Rlc3QiEGZrajNsamFTZGZhbGtyM2oqAlNEMgA=
mpegdashstreaming-cenc-keyserver-playready: true
mpegdashstreaming-cenc-keyserver-playready-system-id: 9a04f079-9840-4286-ab92-e65be0885f95
mpegdashstreaming-cenc-keyserver-playready-license-url: http://myplayreadyserver.com/authenticate.aspx
mpegdashstreaming-cenc-keyserver-playready-checksum: vbxgstjfSQY=

Note: The above keys are fabricated and won't work properly to encrypt/decrypt content. Follow the appropriate PlayReady or Widevine documentation to generate a proper set of keys for testing. For example, for PlayReady you must generate keys using the PlayReady SDK.

The following is a description of each of the items in the key file:

  • mpegdashstreaming-cenc-key-id – The key ID for this asset.
  • mpegdashstreaming-cenc-content-key – The actual content encryption key (128-bit key), Base64 encoded.
  • mpegdashstreaming-cenc-algorithm – The encryption algorithm. The only supported value is AESCTR.
  • mpegdashstreaming-cenc-pssh-version – (Wowza Streaming Engine 4.4.0 and later) The PSSH box version for use with Encrypted Media Extensions (EME). The only supported values are 0 and 1.
  • mpegdashstreaming-cenc-pssh-flags – (Wowza Streaming Engine 4.4.0 and later) The PSSH flags for use with EME. The only supported value is 0.
  • mpegdashstreaming-cenc-keyserver-playready – Must be set to true to enable CENC-PlayReady encryption.
  • mpegdashstreaming-cenc-keyserver-playready-system-id – This optional parameter is the DRM system-specific identifier for PlayReady DRM and must be set to 9a04f079-9840-4286-ab92-e65be0885f95 if provided. This is the value used by the server by default when the parameter isn't provided.
  • mpegdashstreaming-cenc-keyserver-playready-license-url – The PlayReady license URL used by the player to authenticate the player and retrieve the decryption key needed for playback.
  • mpegdashstreaming-cenc-keyserver-playready-checksum – A checksum of the key ID that's needed to authenticate the player.
  • mpegdashstreaming-cenc-keyserver-widevine – Must be set to "true" to enable CENC-Widevine encryption.
  • mpegdashstreaming-cenc-keyserver-widevine-system-id – This optional parameter is the DRM system-specific identifier for Widevine DRM, and must be set to edef8ba9-79d6-4ace-a3c8-27dcd51d21ed if provided. This is the value used by the server by default when the parameter isn't provided.
  • mpegdashstreaming-cenc-keyserver-widevine-pssh-data – Base-64-encoded protection system specific header data for the content.

With a key file in place, the associated MPEG-DASH stream is encrypted using CENC-PlayReady or CENC-Widevine encryption before being delivered. For VOD streaming, when playback begins, you'll see the following log statement to indicate that the session is encrypted:

HTTPStreamerMPEGDashIndexItem.indexFile[vod/_definst_/sample.mp4]: Encrypt MPEGDash: key-id:F6005DCF-7F93-4B8E-85C7-F977740DA059


For live streaming, when MPEG-DASH streaming packetization begins, you'll see a similar message in the log files:

LiveStreamPacketizerMPEGDashStreaming.init[live/_definst_/myStream]: Encrypt MPEGDash: key-id:F6005DCF-7F93-4B8E-85C7-F977740DA059


It's best to get unencrypted live or VOD streaming using MPEG-DASH streaming working before adding encryption. Then, follow these instructions to protect the stream. Another debugging method to ensure the MPEG-DASH stream is CENC-PlayReady or CENC-Widevine protected is to download the DASH MPD file directly to the browser window by entering the MPD URL into the browser address field. If the stream is PlayReady-encrypted, when you look at any audio or video AdaptionSet, you should see a <ContentProtection> element that looks like the following:

<ContentProtection schemeIdUri="urn:uuid:9a04f079-9840-4286-ab92-e65be0885f95" value="Microsoft PlayReady">

If the stream is Widevine-encrypted, then the <ContentProtection> element should look like the following:

<ContentProtection schemeIdUri="urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed" value="Widevine">

On-the-fly CENC-PlayReady encryption using server-side API


Similar to the key files described above, MPEG-DASH streams can be protected using key data passed to Wowza Streaming Engine using the server-side API. There are three methods that can be added to a server-side module to control MPEG-DASH CENC-PlayReady encryption.

VOD streaming CENC-PlayReady protection

public void onHTTPMPEGDashEncryptionKeyVODChunk(HTTPStreamerSessionMPEGDash httpSession, IHTTPStreamerMPEGDashIndex index, CencInfo cencInfo, long chunkId)
{
  // set CENC key info
  cencInfo.setAlgorithm(CencInfo.ALGORITHMID_CTR);
  cencInfo.setKID("F6005DCF-7F93-4B8E-85C7-F977740DA059");
  cencInfo.setEncKeyBytes(Base64.decode("Tc2cQBPC/paTkftvaITCSQ=="));

  // add PlayReady DRM info
  PlayReadyKeyInfo playReadyKeyInfo = new PlayReadyKeyInfo();
  playReadyKeyInfo.setLicenseURL("http://myplayreadyserver.com/authenticate.aspx");
  playReadyKeyInfo.setChecksum(Base64.decode("vbxgstjfSQY="));
  playReadyKeyInfo.setKeyId(BufferUtils.decodeHexString("F6005DCF-7F93-4B8E-85C7-F977740DA059".replace("-", "")));
  playReadyKeyInfo.setContentKey(Base64.decode("Tc2cQBPC/paTkftvaITCSQ=="));
  CencDRMInfoPlayready prInfo = new CencDRMInfoPlayready();
  prInfo.setPlayReadyKeyInfo(playReadyKeyInfo);
  cencInfo.addDRM("myPlayReadyDRM:9a04f079-9840-4286-ab92-e65be0885f95", prInfo);
}

 

Note: To expose the stream name, use the following method instead: public void onHTTPMPEGDashRenditionEncryptionKeyVODChunk(HTTPStreamerSessionMPEGDash httpSession, String streamName, IHTTPStreamerMPEGDashIndex index, CencInfo cencInfo, long chunkId)

Live streaming CENC-PlayReady protection

public void onHTTPMPEGDashEncryptionKeyLiveChunk(ILiveStreamPacketizer liveStreamPacketizer, String streamName, CencInfo cencInfo, long chunkId)
{
  // set CENC key info
  cencInfo.setAlgorithm(CencInfo.ALGORITHMID_CTR);
  cencInfo.setKID("F6005DCF-7F93-4B8E-85C7-F977740DA059");
  cencInfo.setEncKeyBytes(Base64.decode("Tc2cQBPC/paTkftvaITCSQ=="));

  // add PlayReady DRM info
  PlayReadyKeyInfo playReadyKeyInfo = new PlayReadyKeyInfo();
  playReadyKeyInfo.setLicenseURL("http://myplayreadyserver.com/authenticate.aspx");
  playReadyKeyInfo.setChecksum(Base64.decode("vbxgstjfSQY="));
  playReadyKeyInfo.setKeyId(BufferUtils.decodeHexString("F6005DCF-7F93-4B8E-85C7-F977740DA059".replace("-", "")));
  playReadyKeyInfo.setContentKey(Base64.decode("Tc2cQBPC/paTkftvaITCSQ=="));
  CencDRMInfoPlayready prInfo = new CencDRMInfoPlayready();
  prInfo.setPlayReadyKeyInfo(playReadyKeyInfo);
  cencInfo.addDRM("myPlayReadyDRM:9a04f079-9840-4286-ab92-e65be0885f95", prInfo);
}

On-the-fly CENC-Widevine encryption using server-side API


Similar to the key files described above, MPEG-DASH streams can be protected using key data passed to Wowza Streaming Engine using the server-side API. There are three methods that can be added to a server-side module to control MPEG-DASH CENC-Widevine encryption.

VOD streaming CENC-Widevine protection

public void onHTTPMPEGDashEncryptionKeyVODChunk(HTTPStreamerSessionMPEGDash httpSession, IHTTPStreamerMPEGDashIndex index, CencInfo cencInfo, long chunkId)
{
  // set CENC key info
  cencInfo.setAlgorithm(CencInfo.ALGORITHMID_CTR);
  cencInfo.setKID(Base64.decode("ApS5WZ11XeK78P3KP6Xqtw=="));
  cencInfo.setEncKeyBytes(Base64.decode("O9ovQDRMfe9hQie5wPA+Jg=="));

  // add Widevine DRM info
  CencDRMInfoWidevine drmInfo = new CencDRMInfoWidevine();
  drmInfo.setSystemId("edef8ba9-79d6-4ace-a3c8-27dcd51d21ed");
  drmInfo.setPsshData(Base64.decode("CAESEAKUuVmddV3iu/D9yj+l6rcaDXdpZGV2aW5lX3Rlc3QiEGZrajNsamFTZGZhbGtyM2oqAlNEMgA="));
  cencInfo.addDRM("myWidevineDRM:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed", drmInfo);
}

 

Note: To expose the stream name, use the following method instead: public void onHTTPMPEGDashRenditionEncryptionKeyVODChunk(HTTPStreamerSessionMPEGDash httpSession, String streamName, IHTTPStreamerMPEGDashIndex index, CencInfo cencInfo, long chunkId)

Live streaming CENC-Widevine protection

public void onHTTPMPEGDashEncryptionKeyLiveChunk(ILiveStreamPacketizer liveStreamPacketizer, String streamName, CencInfo cencInfo, long chunkId)
{
  // set CENC key info
  cencInfo.setAlgorithm(CencInfo.ALGORITHMID_CTR);
  cencInfo.setKID(Base64.decode("ApS5WZ11XeK78P3KP6Xqtw=="));
  cencInfo.setEncKeyBytes(Base64.decode("O9ovQDRMfe9hQie5wPA+Jg=="));

  // add Widevine DRM info
  CencDRMInfoWidevine drmInfo = new CencDRMInfoWidevine();
  drmInfo.setSystemId("edef8ba9-79d6-4ace-a3c8-27dcd51d21ed");
  drmInfo.setPsshData(Base64.decode("CAESEAKUuVmddV3iu/D9yj+l6rcaDXdpZGV2aW5lX3Rlc3QiEGZrajNsamFTZGZhbGtyM2oqAlNEMgA="));
  cencInfo.addDRM("myWidevineDRM:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed", drmInfo);
}

On-the-fly CENC-Multi-DRM encryption using server-side API


MPEG-DASH streams can be protected using key data supported by multiple DRM systems, which can be passed to Wowza Streaming Engine using the server-side API. There are three methods that can be added to a server-side module to control MPEG-DASH CENC encryption for multiple DRM systems.

VOD streaming CENC-Widevine+PlayReady protection

public void onHTTPMPEGDashEncryptionKeyVODChunk(HTTPStreamerSessionMPEGDash httpSession, IHTTPStreamerMPEGDashIndex index, CencInfo cencInfo, long chunkId)
{
  // set CENC key info
  cencInfo.setAlgorithm(CencInfo.ALGORITHMID_CTR);
  cencInfo.setKID(Base64.decode("ApS5WZ11XeK78P3KP6Xqtw=="));
  cencInfo.setEncKeyBytes(Base64.decode("O9ovQDRMfe9hQie5wPA+Jg=="));

  // add DRM info for first DRM system (Widevine in this case)
  CencDRMInfoWidevine drmInfo = new CencDRMInfoWidevine();
  drmInfo.setSystemId("edef8ba9-79d6-4ace-a3c8-27dcd51d21ed");
  drmInfo.setPsshData(Base64.decode("CAESEAKUuVmddV3iu/D9yj+l6rcaDXdpZGV2aW5lX3Rlc3QiEGZrajNsamFTZGZhbGtyM2oqAlNEMgA="));
  cencInfo.addDRM("myWidevineDRM:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed", drmInfo);

  // add DRM info for second DRM system (PlayReady in this case)
  PlayReadyKeyInfo playReadyKeyInfo = new PlayReadyKeyInfo();
  playReadyKeyInfo.setLicenseURL("http://myplayreadyserver.com/authenticate.aspx");
  playReadyKeyInfo.setChecksum(Base64.decode("vbxgstjfSQY="));
  playReadyKeyInfo.setKeyId(BufferUtils.decodeHexString("F6005DCF-7F93-4B8E-85C7-F977740DA059".replace("-", "")));
  playReadyKeyInfo.setContentKey(Base64.decode("Tc2cQBPC/paTkftvaITCSQ=="));
  CencDRMInfoPlayready prInfo = new CencDRMInfoPlayready();
  prInfo.setPlayReadyKeyInfo(playReadyKeyInfo);
  cencInfo.addDRM("myPlayReadyDRM:9a04f079-9840-4286-ab92-e65be0885f95", prInfo);
}

 

Note: To expose the stream name, use the following method instead: public void onHTTPMPEGDashRenditionEncryptionKeyVODChunk(HTTPStreamerSessionMPEGDash httpSession, String streamName, IHTTPStreamerMPEGDashIndex index, CencInfo cencInfo, long chunkId)

Live streaming CENC-Widevine+PlayReady protection

public void onHTTPMPEGDashEncryptionKeyLiveChunk(ILiveStreamPacketizer liveStreamPacketizer, String streamName, CencInfo cencInfo, long chunkId)
{
  // set CENC key info
  cencInfo.setAlgorithm(CencInfo.ALGORITHMID_CTR);
  cencInfo.setKID(Base64.decode("ApS5WZ11XeK78P3KP6Xqtw=="));
  cencInfo.setEncKeyBytes(Base64.decode("O9ovQDRMfe9hQie5wPA+Jg=="));

  // add DRM info for first DRM system (Widevine in this case)
  CencDRMInfoWidevine drmInfo = new CencDRMInfoWidevine();
  drmInfo.setSystemId("edef8ba9-79d6-4ace-a3c8-27dcd51d21ed");
  drmInfo.setPsshData(Base64.decode("CAESEAKUuVmddV3iu/D9yj+l6rcaDXdpZGV2aW5lX3Rlc3QiEGZrajNsamFTZGZhbGtyM2oqAlNEMgA="));
  cencInfo.addDRM("myWidevineDRM:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed", drmInfo);

  // add DRM info for second DRM system (PlayReady in this case)
  PlayReadyKeyInfo playReadyKeyInfo = new PlayReadyKeyInfo();
  playReadyKeyInfo.setLicenseURL("http://myplayreadyserver.com/authenticate.aspx");
  playReadyKeyInfo.setChecksum(Base64.decode("vbxgstjfSQY="));
  playReadyKeyInfo.setKeyId(BufferUtils.decodeHexString("F6005DCF-7F93-4B8E-85C7-F977740DA059".replace("-", "")));
  playReadyKeyInfo.setContentKey(Base64.decode("Tc2cQBPC/paTkftvaITCSQ=="));
  CencDRMInfoPlayready prInfo = new CencDRMInfoPlayready();
  prInfo.setPlayReadyKeyInfo(playReadyKeyInfo);
  cencInfo.addDRM("myPlayReadyDRM:9a04f079-9840-4286-ab92-e65be0885f95", prInfo);
}

Server-side API method details


The VOD methods, onHTTPMPEGDashEncryptionKeyVODChunk() and onHTTPMPEGDashRenditionEncryptionKeyVODChunk(), are initially called at the start of the respective VOD session, at which point the chunkId field is set to -1. The live method, onHTTPMPEGDashEncryptionKeyLiveChunk(), is initially called at stream initialization when packetization starts (not session-based), at which point the chunkId field is set to -1.

The module should populate the CencInfo data (and internal PlayReadyKeyInfo data for CENC-PlayReady) in a manner similar to the examples in this article. The methods are subsequently called again repeatedly each time a DASH chunk is about to be created and the chunkId field is set to the respective chunk number.

In the initial call where chunkId is -1, a call to cencInfo.getKID() should return null. On all ensuing calls for that session, cencInfo.getKID() returns the KID value that was last provided to allow the module to verify or replace it with a new key. The CENC data is remembered across each call for the session with VOD streams and each call for the stream with live streams.

With the VOD methods, the module could, therefore, provide the encryption info on just the initial call to provide session-based keys, or on periodic calls to emulate key rotation. With the live method, if the module doesn't change the key on the ensuing calls, then all live client connections get the same set of chunks encrypted with the same key. The module could optionally provide encryption info on periodic calls to emulate key rotation.

If you don't provide key information when the method is called, then the stream won't be encrypted.

Testing playback


The following players can play either our PlayReady-encrypted or Widevine-encrypted DASH streams using the static key and protection system specific header (pssh) information provided in the examples above, using either key files or module APIs:

 

  • The Google Dash JavaScript Player reference player can play our Widevine-encrypted on demand DASH streams in the Google Chrome web browser. This player cannot play our encrypted live DASH streams.
     
  • The Google open-source reference player (Shaka Player) can play our Widevine-encrypted live and on-demand DASH streams in the Chrome browser.
  • The Bitmovin Adaptive Streaming Player can play our Widevine-encrypted live and on-demand DASH streams in Google Chrome and PlayReady-encrypted live and on-demand DASH streams in Internet Explorer 11 (on Windows 8.1 and Windows 10) and Microsoft Edge on Windows 10. 

Notes


  • You can resolve all of the classes used by the examples in this article by using the following imports:
     
    import com.wowza.wms.module.ModuleBase;
    import com.wowza.util.*;
    import com.wowza.wms.drm.cenc.*;
    import com.wowza.wms.drm.playready.*;
    import com.wowza.wms.httpstreamer.mpegdashstreaming.file.IHTTPStreamerMPEGDashIndex;
    import com.wowza.wms.httpstreamer.mpegdashstreaming.httpstreamer.HTTPStreamerSessionMPEGDash;
    import com.wowza.wms.stream.livepacketizer.ILiveStreamPacketizer;
  • There are multiple ways to set the KID in the CencInfo class:
     
    // if KID is base64-encoded:
    cencInfo.setKID(Base64.decode("ApS5WZ11XeK78P3KP6Xqtw=="));
    // if KID is in byte buffer form:
    cencInfo.setKID(<kidByteBuffer>);
    // if KID is uuid form:
    cencInfo.setKID("F6005DCF-7F93-4B8E-85C7-F977740DA059");
  • There are multiple ways to set the encryption key in the CencInfo class:
     
    // if key is base64-encoded:
    cencInfo.setEncKeyBytes(Base64.decode("O9ovQDRMfe9hQie5wPA+Jg=="));
    // if key is in byte buffer form:
    cencInfo.setEncKeyBytes(<keyByteBuffer>)
  • When using this option, you must provide your own PlayReady or Widevine key server.
  • Key rotation using PlayReady Embedded License Stores (ELS) is not supported.
  • Providing MPEG-DASH CENC key information through a key file for a given on demand or live stream will result in the associated module API above to never be called for that stream.