如何使用ClearKey加密WebM或MP4文件,然后播放

时间:2015-11-10 14:25:21

标签: html5 html5-video drm webm mpeg-dash

我目前正在研究加密和在浏览器中播放加密视频的主题。在使用Widevine时,我已经与castlabs的DRMToday和Shaka Player取得了一些成功。

现在我正在尝试使用ClearKey在没有外部服务的情况下加密视频并在Chrome中播放(使用可以处理的任何js播放器)。

我确实设法使用MP4Box加密单个mp4文件(并且mse-eme用于创建加密配置),但我不知道如何在浏览器中播放它。 HTML5的视频甚至没有触发“加密”事件。加密本身工作正常 - 我能够使用相同的工具使用正确的密钥对其进行解密。

我尝试用这个加密文件创建一个DASH并在Shaka Player中播放。我使用MP4Box创建了清单。我不得不手动将缺少的xmlns添加到此文件中(xmlns:cenc =“urn:mpeg:cenc:2013”​​),以便DOMParser正确解析它。我不知道如何处理许可证。

我发现很少有播放编码的webm文件的例子(包括Shaka Player的演示页面)。如何加密webm文件?我确实找到了https://github.com/webmproject/webm-tools,但似乎需要构建整个Chromium才能工作。

还有其他可以加密webm文件的工具吗?

3 个答案:

答案 0 :(得分:2)

这是我测试ClearKey DRM播放的文件集。

mp4box(gpac)drm.xml规范文件,您可以在init.mp4段内生成一个或多个PSSH表。

<?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>

命令行加密视频+音频和拆分细分。

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
用于ClearKey播放的

ShakaPlayer独立演示,使用Chrome或Firefox。我在互联网上找到了这个源代码,所以不管是谁做的。

<!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“许可证服务器php脚本”,播放器发送一个json文档,此脚本返回KID = KEY配对。

<?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;

?>
使用CENC和ClearKey contentprotection元素

清单

<?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>

清单cenc:pssh 包含KID值,如果只使用一个密钥,则很容易生成。请参阅pssh元素值的base64tohexdump。

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

答案 1 :(得分:1)

您可以使用Azure媒体服务在不同的流协议(HLS,平滑流和MPEG-DASH)中使用AES清除密钥动态加密多比特率MP4。您不必自己构建加密器。我们还有一个播放器可以在所有浏览器中播放AES加密内容 - 例如在现代浏览器中使用DASH的AES,在Safari中使用HLS的AES和在旧浏览器中使用Flash平滑流式传输的AES。

您可以在此处查看示例:http://amsplayer.azurewebsites.net/azuremediaplayer.html。并选择与AES相关的样本流。您可以按照本教程https://azure.microsoft.com/en-us/documentation/articles/media-services-protect-with-aes128/配置视频的AES加密。

答案 2 :(得分:0)

要具体回答“ HTML5的视频甚至都不会触发其上的“加密”事件。 ”-在2019年,Chrome不会触发经过加密的事件,除非MSE与视频配合使用时,Firefox会显示一条错误消息,指示EME在没有MSE的情况下无法使用。 因此,要播放加密的视频,您必须使用媒体源扩展

这一点在文档中并不明显,但在此处突出显示 https://github.com/cpearce/eme-in-non-fragmented-mp4

使用正确加密的媒体,您可以在MSE中将单个MP4文件添加为“段”并以chrome格式播放(例如,使用此https://github.com/cpearce/mse-eme的修改版)(我的测试仅是clearkey,使用的是bento4到第一个片段,然后加密单个MP4文件-不要将片段与段混淆。 这不是非常有效,因为我假设在开始播放之前就已下载了整个文件(并将其保存在浏览器的内存中),即,这与直接html5视频元素播放不同,浏览器使用范围请求并管理文件下载和内存使用情况