How to encrypt WebM or MP4 file using ClearKey and then play it

好久不见. 提交于 2019-12-21 20:39:51

问题


I'm currently researching the subject of encrypting and playing encrypted videos in browser. I already have some successes with castlabs' DRMToday and Shaka Player while using Widevine.

Now I'm trying to encrypt video without external services using ClearKey and play it in Chrome (using whatever js player which can handle).

I did manage to encrypt single mp4 file using MP4Box (and mse-eme for creating crypt configuration) but I have no idea how to play it in browser. HTML5's Video didn't even trigger "encrypted" event on it. Encryption itself works fine - I was able to decrypt it back using the same tool with proper key.

I tried to create a DASH out of this encrypted file and play it in Shaka Player. I created manifest using MP4Box. I had to manually add a missing xmlns to this file (xmlns:cenc="urn:mpeg:cenc:2013") so DOMParser parses it properly. I don't know how should I handle the license.

I found few working examples of playing encoded webm files (including Shaka Player's demo page). How can I encrypt webm file? I did found https://github.com/webmproject/webm-tools but it seems to require building entire Chromium in order to work.

Are there any other tools that can encrypt webm files?


回答1:


This is my set of files to test ClearKey DRM playback.

mp4box(gpac) drm.xml specification file where you could give one or more PSSH tables to be generated inside the init.mp4 segments.

<?xml version="1.0" encoding="UTF-8" ?>
<GPACDRM type="CENC AES-CTR">
<!-- 
  kid=0x43215678123412341234123412341234
  key=0x12341234123412341234123412341234
  iv=0x22ee7d4745d3a26a
--> 

<!-- CENC -->
<DRMInfo type="pssh" version="1">
  <BS ID128="1077efecc0b24d02ace33c1e52e2fb4b"/>
  <BS bits="32" value="1"/>
  <BS ID128="43215678123412341234123412341234"/>
</DRMInfo>

<CrypTrack trackID="1" IsEncrypted="1" IV_size="8" first_IV="0x22ee7d4745d3a26a" saiSavedBox="senc">
  <key KID="0x43215678123412341234123412341234" value="0x12341234123412341234123412341234"/>
</CrypTrack>

</GPACDRM>

Command lines to encrypt video+audio and split segments.

MP4Box.exe -crypt gpacdrm.xml temp-v1.mp4 -out ./drm/temp-v1.mp4
MP4Box.exe -crypt gpacdrm.xml temp-a1.mp4 -out ./drm/temp-a1.mp4
MP4Box.exe -dash 6000 -frag 6000 -mem-frags -rap -profile dashavc264:live -profile-ext urn:hbbtv:dash:profile:isoff-live:2012 -min-buffer 3000  -bs-switching no -sample-groups-traf -single-traf -subsegs-per-sidx 1 -segment-name $RepresentationID$_$Number$$Init=i$ -segment-timeline -out manifest.mpd temp-v1.mp4#trackID=1:id=v1:period=p0 temp-a1.mp4#trackID=1:id=a1:period=p0

ShakaPlayer standalone demo for ClearKey playback, use Chrome or Firefox. I found this source code in an internet so credits to whoever did it.

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <script src="https://cdnjs.cloudflare.com/ajax/libs/shaka-player/2.1.0/shaka-player.compiled.js"></script>
    <title>MPEG-DASH Player Test</title>
    <script>
        var manifestUrl = 'https://my.server.com/drm/manifest_clearkey.mpd';
        var laUrl       = 'https://my.server.com/drm/laurl_ck.php';

        function initApp() {
            // Install built-in polyfills to patch browser incompatibilities.
            shaka.polyfill.installAll();

            // Check to see if the browser supports the basic APIs Shaka needs.
            if (shaka.Player.isBrowserSupported()) {
                // Everything looks good!
                initPlayer();
            } else {
                // This browser does not have the minimum set of APIs we need.
                console.error('Browser not supported!');
            }
        }

        function initPlayer() {
            // Create a Player instance.
            var video = document.getElementById('video');
            var player = new shaka.Player(video);

            // Configue
            player.configure({
                drm: {
                    servers: {
                        'org.w3.clearkey': laUrl
                    },
                    clearKeys: {
                        //'kid': 'key'
                    }
                }
            });

            // Attach player to the window to make it easy to access in the JS console.
            window.player = player;

            // Listen for error events.
            player.addEventListener('error', onErrorEvent);

            // Try to load a manifest.
            // This is an asynchronous process.
            player.load(manifestUrl).then(function () {
                // This runs if the asynchronous load is successful.
                console.log('The video has now been loaded!');
            }).catch(onError);  // onError is executed if the asynchronous load fails.
        }

        function onErrorEvent(event) {
            // Extract the shaka.util.Error object from the event.
            onError(event.detail);
        }

        function onError(error) {
            console.error('Error code', error.code, 'object', error);
            alert("ErrorCode="+error.code);
        }

        document.addEventListener('DOMContentLoaded', initApp);
    </script>
</head>
<body>
    <video id="video" autoplay controls></video>
</body>
</html>

ClearKey DRM "license server php script", player sends a json document and this script returns KID=KEY pairing.

<?php
header( "Expires: Mon, 20 Dec 1998 01:00:00 GMT" );
header( "Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT" );
header( "Cache-Control: no-cache, must-revalidate" );
header( "Pragma: no-cache" );
header('Access-Control-Allow-Origin: *');
header('Access-Control-Allow-Headers: origin,range,accept,accept-encoding,referer,content-type, SOAPAction,X-AxDRM-Message');
header('Access-Control-Allow-Methods: GET,HEAD,OPTIONS,POST');
header('Access-Control-Expose-Headers: server,range,content-range,content-length,content-type');
// write content-type header after OPTIONS check
// ClearKey DRM server

// some players may submit OPTIONS request(zero-length) before POST drm.xml submit
if ($_SERVER['REQUEST_METHOD']=="OPTIONS") {
    header("Content-Length: 0");
    header("Expires: -1");
    return;
}
//header('Content-Type: text/plain; charset=utf-8');
header('Content-Type: application/json; charset=utf-8');

// Request may have one or more KIDs(base64), read first KID from the request for now.
// KID base64 is without trailing "=" padding chars.
// Request : {"kids":["QyFWeBI0EjQSNBI0EjQSNA"],"type":"temporary"}
// Response: {"keys": [{"k": "EjQSNBI0EjQSNBI0EjQSNA", "kty": "oct", "kid": "QyFWeBI0EjQSNBI0EjQSNA" }], "type": "temporary"}
$req = file_get_contents('php://input'); // read POST bodypart
$json= json_decode($req);
$kidb= $json->{"kids"}[0]; // base64 format
$kid = bin2hex(base64_decode($kidb, true)); // hex format

// KID=KEY lookup table, find KEY and base64(trim trailing "==" chars)
// "EjQSNBI0EjQSNBI0EjQSNA==" -> "EjQSNBI0EjQSNBI0EjQSNA"
$keys = array(
  "43215678123412341234123412341234" => "12341234123412341234123412341234",
  "43215678123412341234123412341235" => "12341234123412341234123412341235",
  "43215678123412341234123412341236" => "12341234123412341234123412341236",
  "43215678123412341234123412341237" => "12341234123412341234123412341237",
  "43215678123412341234123412341238" => "12341234123412341234123412341238"
);
$key = base64_encode(hex2bin($keys[$kid]));
$key = str_replace("=", "", $key);

$data = "{\"keys\": [{\"k\": \$key, \"kty\": \"oct\", \"kid\": \$kid }], \"type\": \"temporary\"}";
$data = str_replace("\$key", "\"".$key."\"", $data);
$data = str_replace("\$kid", "\"".$kidb."\"", $data);

echo $data;

?>

Manifest with CENC and ClearKey contentprotection elements.

<?xml version="1.0" encoding="UTF-8"?>
<MPD xmlns="urn:mpeg:dash:schema:mpd:2011" xmlns:cenc="urn:mpeg:cenc:2013" xmlns:mas="urn:marlin:mas:1-0:services:schemas:mpd" xmlns:mspr="urn:microsoft:playready" maxSegmentDuration="PT0H0M6.000S" mediaPresentationDuration="PT0H1M30.000S" minBufferTime="PT3.000S" profiles="urn:mpeg:dash:profile:isoff-live:2011,http://dashif.org/guidelines/dash264,urn:hbbtv:dash:profile:isoff-live:2012" type="static">


 <Period duration="PT0H1M30.000S" id="p0">
  <AdaptationSet lang="und" maxFrameRate="25" maxHeight="360" maxWidth="640" par="16:9" segmentAlignment="true" startWithSAP="1">
   <ContentProtection cenc:default_KID="43215678-1234-1234-1234-123412341234" schemeIdUri="urn:mpeg:dash:mp4protection:2011" value="cenc"/>
<ContentProtection schemeIdUri="urn:uuid:1077efec-c0b2-4d02-ace3-3c1e52e2fb4b">
  <cenc:pssh>AAAANHBzc2gBAAAAEHfv7MCyTQKs4zweUuL7SwAAAAFDIVZ4EjQSNBI0EjQSNBI0AAAAAA==</cenc:pssh>
</ContentProtection>
   <SegmentTemplate initialization="$RepresentationID$_i.mp4" media="$RepresentationID$_$Number$.m4s" startNumber="1" timescale="1000">
    <SegmentTimeline>
     <S d="6000" r="14" t="0"/>
    </SegmentTimeline>
   </SegmentTemplate>
   <Representation bandwidth="491773" codecs="avc1.4D4028" frameRate="25" height="360" id="v1" mimeType="video/mp4" sar="1:1" width="640">
   </Representation>
  </AdaptationSet>
  <AdaptationSet lang="und" segmentAlignment="true" startWithSAP="1">
   <ContentProtection cenc:default_KID="43215678-1234-1234-1234-123412341234" schemeIdUri="urn:mpeg:dash:mp4protection:2011" value="cenc"/>
<ContentProtection schemeIdUri="urn:uuid:1077efec-c0b2-4d02-ace3-3c1e52e2fb4b">
  <cenc:pssh>AAAANHBzc2gBAAAAEHfv7MCyTQKs4zweUuL7SwAAAAFDIVZ4EjQSNBI0EjQSNBI0AAAAAA==</cenc:pssh>
</ContentProtection>
   <SegmentTemplate initialization="$RepresentationID$_i.mp4" media="$RepresentationID$_$Number$.m4s" startNumber="1" timescale="1000">
    <SegmentTimeline>
     <S d="5973" t="0"/>
     <S d="5995" r="1"/>
     <S d="5994"/>
     <S d="5995" r="1"/>
     <S d="5994"/>
     <S d="5995"/>
     <S d="5994"/>
     <S d="5995" r="2"/>
     <S d="5994"/>
     <S d="5995" r="1"/>
     <S d="101"/>
    </SegmentTimeline>
   </SegmentTemplate>
   <Representation audioSamplingRate="48000" bandwidth="133119" codecs="mp4a.40.2" id="a1" mimeType="audio/mp4">
    <AudioChannelConfiguration schemeIdUri="urn:mpeg:dash:23003:3:audio_channel_configuration:2011" value="2"/>
   </Representation>
  </AdaptationSet>
 </Period>
</MPD>

Manifest cenc:pssh contains KID values and is easy to generate if just one key is used. See this base64tohexdump of pssh element value.

00 00 00 34 70 73 73 68 01 00 00 00 
10 77 EF EC C0 B2 4D 02 AC E3 3C 1E 52 E2 FB 4B 
00 00 00 01 
43 21 56 78 12 34 12 34 12 34 12 34 12 34 12 34 
00 00 00 00



回答2:


You could use Azure Media Services to dynamically encrypt a multi-bitrate MP4 with AES clear key in different streaming protocols (HLS, Smooth Streaming and MPEG-DASH). You don't have to build the encryptor yourself. We also have a player to playback AES encrypted content in all browsers - for e.g. AES with DASH in modern browsers, AES with HLS in Safari and AES with Smooth streaming with Flash in old browsers.

You can view a example here: http://amsplayer.azurewebsites.net/azuremediaplayer.html. And Choose an AES related sample stream. You can configure the AES encryption for your video by following this tutorial: https://azure.microsoft.com/en-us/documentation/articles/media-services-protect-with-aes128/.




回答3:


To specifically answer "HTML5's Video didn't even trigger "encrypted" event on it." - in 2019, Chrome will not trigger the onencrypted event unless MSE is in use with the video, and Firefox shows an error indicating EME does not work without MSE. So TO PLAY ENCRYPTED VIDEO, YOU MUST USE MEDIA SOURCE EXTENSIONS.

This is not at all obvious in the documentation, but is highlighted here https://github.com/cpearce/eme-in-non-fragmented-mp4

With a correctly encrypted media, you CAN add a single MP4 file as a 'segment' in MSE and play back in chrome (e.g. with modified version of this https://github.com/cpearce/mse-eme) (my testing was clearkey only, used bento4 to first fragment, then encrypt the single MP4 file - don't confuse fragment with segment....). This is not very efficient, as I assume that the whole file is downloaded (and kept in browser memory) before playback can start, i.e. it's not like a straight html5 video element playback where the browser uses range requests and manages file download and memory usage.



来源:https://stackoverflow.com/questions/33632240/how-to-encrypt-webm-or-mp4-file-using-clearkey-and-then-play-it

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!