如何使用ClearKey加密WebM或MP4文件并播放

5

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

现在我正在尝试使用ClearKey加密视频并在Chrome中播放(使用任何可以处理的js播放器)。

我成功地使用MP4Box(以及mse-eme用于创建加密配置)加密了单个mp4文件,但我不知道如何在浏览器中播放它。HTML5的Video甚至没有触发“encrypted”事件。加密本身运行良好-我能够使用正确的密钥将其解密回来。

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

我找到了一些播放编码的webm文件的工作示例(包括Shaka Player的演示页面)。我怎样才能加密webm文件?我确实找到了https://github.com/webmproject/webm-tools,但似乎需要构建整个Chromium才能使用。

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


你好,你解决问题了吗?我和你一样面临同样的问题。 - Nam Pham
目前我使用了Bento4工具(用于创建加密的DASH文件)和名为ezdrm的外部服务,以widevine进行加密。但这并不是我的首选推荐。对于widevine,您需要第三方服务。您可以直接与Google打交道,但支付第三方提供商的费用要便宜得多。 - Xander
我没有跟随 Clear Key 的主题。 - Xander
3个回答

4
这是我的一组测试ClearKey DRM播放的文件。 mp4box(gpac) drm.xml规范文件,您可以在其中提供一个或多个PSSH表以生成在init.mp4片段内部。
<?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

ShakaPlayer独立演示版,用于ClearKey播放,请使用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内容保护元素的清单。

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

我正在尝试加密我的视频。当我尝试您的教程或从dash示例中清除密钥时,一切都正常。但是当我生成自己的十六进制字符串时,解密在浏览器中无法工作。 - ashutosh singh
@ashutoshsingh 近期一些浏览器已经改变了 ClearKey LaUrl 的请求-响应格式,因此我的 laurl_ck.php 脚本可能无法再正常工作。请参考这个 dashsj 示例,其中在 HTML 代码中使用了一个嵌入式的 KID+KEY,而没有使用 laurl。https://m.dtv.fi/dash/index_clearkey2b.html?video=33 - Whome

2
具体回答“HTML5的视频甚至没有触发它的“加密”事件” - 在2019年,除非MSE与视频一起使用,否则Chrome不会触发onencrypted事件,而Firefox显示一个错误,表明EME在没有MSE的情况下不起作用。 因此,为了播放加密视频,必须使用媒体源扩展(MSE)。
这在文档中并不明显,但在https://github.com/cpearce/eme-in-non-fragmented-mp4中有所强调。
使用正确加密的媒体,您可以将单个MP4文件作为MSE中的“片段”添加,并在Chrome中播放(例如使用此修改版的https://github.com/cpearce/mse-eme)(我的测试仅使用了clearkey,使用bento4首先对单个MP4文件进行分段,然后进行加密 - 不要将片段与分段混淆...)。 这并不是非常高效的,因为我认为整个文件在播放之前会被下载(并保留在浏览器内存中),即它不像直接使用html5视频元素播放那样,浏览器使用范围请求并管理文件下载和内存使用。

1
您可以使用Azure媒体服务在不同的流媒体协议(HLS、Smooth Streaming和MPEG-DASH)中,使用AES明文密钥动态加密多比特率MP4。您无需自己构建加密器。我们还有一个播放器,可以在所有浏览器中回放AES加密内容 - 例如,现代浏览器中的DASH AES,Safari中的HLS AES和旧浏览器中的Smooth streaming AES with Flash。

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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接