• How to secure MPEG-DASH streaming using Common Encryption (CENC)

    Wowza Streaming Engine™ software can protect on demand 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.

    Many of the descriptions in this article assume a familiarity with Common Encryption 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.

    Contents


    Wowza DRM
    On-the-fly DASH CENC encryption using key files
    On-the-fly CENC-PlayReady encryption using server-side API
    On-the-fly CENC-Widevine encryption using server-side API
    On-the-fly CENC-Multi-DRM encryption using server-side API
    Testing playback
    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 Wowza DRM overview.

    On-the-fly DASH CENC encryption using key files


    On demand and live content can be CENC-PlayReady or CENC-Widevine encrypted on the fly using key files. When using this option, you must provide your own 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 following path:
    [install-dir]/keys/myStream.key
    To protect the on demand stream sample.mp4, you would create a key file with the following path:
    [install-dir]/keys/sample.mp4.key
    The naming is similar for live and on demand 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, you would create a key file with the following 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-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 special checksum of the key ID that's needed to authenticate the player.
    • mpegdashstreaming-cenc-widevine: Must be set to "true" to enable CENC-Widevine encryption.
    • mpegdashstreaming-cenc-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-widevine-pssh-data: Base-64-encoded protection system specific header data for the content.
    • mpegdashstreaming-cenc-pssh-version: The PSSH box version for use with Encrypted Media Extensions (EME). The only supported values are 0 and 1. (Wowza Streaming Engine 4.4.0 and later)
    • mpegdashstreaming-cenc-pssh-flags: The PSSH flags for use with EME. The only supported value is 0. (Wowza Streaming Engine 4.4.0 and later)

    With a key file in place, the associated MPEG-DASH stream is encrypted using CENC-PlayReady or CENC-Widevine encryption before being delivered. For on demand 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 on demand streaming using MPEG-DASH streaming working first by following one of our Tutorials.

    After you have this working, then you can 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> elements 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 two methods (one for on demand and one for live) that can be added to a server-side module to control MPEG-DASH CENC-PlayReady encryption.

    On demand 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);
    }

    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 two methods (one for on demand and one for live) that can be added to a server-side module to control MPEG-DASH CENC-Widevine encryption.

    On demand 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);

    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 two methods (one for on demand and one for live) that can be added to a server-side module to control MPEG-DASH CENC encryption for multiple DRM systems.

    On demand 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);
    }

    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);
    }

    onHTTPMPEGDashEncryptionKeyVODChunk()

    This API is initially called at the start of the respective VOD session, 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 example above.

    onHTTPMPEGDashEncryptionKeyVODChunk() is subsequently called again repeatedly, once for each chunk, 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 to onHTTPMPEGDashEncryptionKeyVODChunk() 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 that session). 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.

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

    onHTTPMPEGDashEncryptionKeyLiveChunk()

    This API 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 example above.

    onHTTPMPEGDashEncryptionKeyLiveChunk() is subsequently called again 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 to onHTTPMPEGDashEncryptionKeyLiveChunk() 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 that stream). 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


    Historically there have been limited options for testing playback of encrypted MPEG-DASH streams outside of player SDKs provided by the DRM vendors themselves. That said, there are a few options. 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:


    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.
    • For an excellent introduction to Common Encryption within MPEG-DASH, see MPEG-DASH & Common Encryption: The Promise of Broad Reach and Interoperability.

    Originally Published: 02-11-2014.
    Updated: For Wowza Streaming Engine 4.4.0 on 02-02-2016.

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