DRM(数字权限管理技术)防截屏录屏----解密org.w3.clearkey视频并播放

提示:解密org.w3.clearkey视频并播放
帮助:未实现clearkey加密,如有大神,请指导一下

前言

‌‌ClearKey‌是一种基于JSON Web Key (JWK)格式的内容加密方案,主要用于在Web环境中保护媒体内容的安全传输和播放。ClearKey通过使用JSON Web Tokens (JWT)来传输密钥信息,确保只有持有正确密钥的用户才能解密和播放受保护的媒体内容。

ClearKey的工作原理
‌1、密钥管理‌:ClearKey使用JWT来传输密钥信息,JWT包含三个部分:头部、有效载荷和签名。头部描述了令牌的类型和加密方法,有效载荷包含密钥信息,签名用于验证令牌的真实性。
2‌、密钥获取‌:在播放受保护的媒体内容时,浏览器会向服务器请求JWT,服务器验证请求者的身份后,返回包含密钥信息的JWT。
3‌、解密播放‌:浏览器使用JWT中的密钥信息对媒体内容进行解密,然后通过MediaSource Extensions (MSE)进行播放。

一、教程

mse-eme教程

二、org.w3.clearkey视频播放

test.html

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <video preload="auto" id="video" controls></video>
    <script>
        const KEYSYSTEM_TYPE = "org.w3.clearkey";
        var videoFragments = [
            // demo 视频
            "video/init.mp4",
            "video/seg-1.mp4",
            "video/seg-2.mp4",
            "video/seg-3.mp4",
            "video/seg-4.mp4",
            "video/seg-5.mp4",
            "video/seg-6.mp4",
            "video/seg-7.mp4"
        ];
        var name = 'video';
        // 音频类型
        const audioContentType = 'audio/mp4; codecs="mp4a.40.2"'; // AAC-LC
        // 视频类型
        const videoContentType = 'video/mp4; codecs="avc1.42E01E"'
            // 创建mediaSource
        const videoSource = new MediaSource();
        var keys = {
            "2fef8ad812df429783e9bf6e5e493e53": "7f412f0575f44f718259beef56ec7771",
            "7eaa636ee7d142fd945d1f764877d8db": "624db3d757bb496fb93e51f341d11716"
        };

        async function load() {
            const videoEl = document.getElementById('video');
            videoEl.src = URL.createObjectURL(videoSource);
            videoEl.sessions = [];
            // 解码
            videoEl.addEventListener("encrypted", async(ev) => {
                let options = [{
                    initDataTypes: ["cenc"],
                    videoCapabilities: [{
                        contentType: videoContentType
                    }],
                    audioCapabilities: [{
                        contentType: audioContentType
                    }]
                }];
                if (typeof(MediaKeySystemAccess.prototype.getConfiguration) == "undefined") options = [{
                    initDataType: "cenc",
                    videoType: videoContentType,
                    audioType: audioContentType
                }];
                // 获取系统允许
                let systemAccess = await navigator.requestMediaKeySystemAccess(KEYSYSTEM_TYPE, options);
                // 获取mediaKeys
                var mediaKeys = await systemAccess.createMediaKeys();
                // 设置播放器mediaKeys
                await videoEl.setMediaKeys(mediaKeys);
                // 生成session
                let session = await videoEl.mediaKeys.createSession();
                videoEl.sessions.push(session);
                // 获取请求
                let request = await session.generateRequest(ev.initDataType, ev.initData)
                session.addEventListener("message", async(e) => {
                    let message = ArrayBufferToString(e.message);
                    let msg = JSON.parse(message);
                    let outKeys = [];
                    for (let i = 0; i < msg.kids.length; i++) {
                        // 获取单个message的id
                        let id = msg.kids[i];
                        // 获取全小写的hex格式的id
                        let hexId = Base64ToHex(id).toLowerCase();
                        // 获取keys中键为hexId的值 
                        let key = keys[hexId];
                        if (key) outKeys.push({
                            "kty": "oct",
                            "alg": "A128KW",
                            "kid": id,
                            "k": HexToBase64(key)
                        });
                    }
                    if (outKeys.length < 1) return console.log('解码失败!!!')
                    let update = JSON.stringify({
                        "keys": outKeys,
                        "type": msg.type
                    });
                    await e.target.update(StringToArrayBuffer(update));
                });
                session.addEventListener("keystatuseschange", (e) => {});
            })
            videoSource.addEventListener('sourceopen', openSourceFile)
        }
        // Hex转base64
        function HexToBase64(hex) {
            var bin = "";
            for (var i = 0; i < hex.length; i += 2) {
                bin += String.fromCharCode(parseInt(hex.substr(i, 2), 16));
            }
            return window.btoa(bin).replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_");
        }
        // base64转Hex
        function Base64ToHex(str) {
            var bin = window.atob(str.replace(/-/g, "+").replace(/_/g, "/"));
            var res = "";
            for (var i = 0; i < bin.length; i++) {
                res += ("0" + bin.charCodeAt(i).toString(16)).substr(-2);
            }
            return res;
        }
        // buffer转字符串
        function ArrayBufferToString(arr) {
            var str = '';
            var view = new Uint8Array(arr);
            for (var i = 0; i < view.length; i++) {
                str += String.fromCharCode(view[i]);
            }
            return str;
        }
        // 字符串转buffer
        function StringToArrayBuffer(str) {
            var arr = new ArrayBuffer(str.length);
            var view = new Uint8Array(arr);
            for (var i = 0; i < str.length; i++) {
                view[i] = str.charCodeAt(i);
            }
            return arr;
        }
        // 打开文件
        async function openSourceFile() {
            videoSource.removeEventListener('sourceopen', openSourceFile);
            let sourceBuffer = videoSource.addSourceBuffer(videoContentType);
            let bufferList = [];
            let count = 0;
            if (videoSource.readyState == 'closed') return console.log('视频readyState为关闭');
            // for循环里直接appendBuffer会报错
            for (let i = 0; i < videoFragments.length; i++) {
                const buffer = await loadFile(videoFragments[i]);
                if (buffer) bufferList.push(buffer);
            }
            // 首次appendBuffer
            await sourceBuffer.appendBuffer(bufferList[count]);
            sourceBuffer.addEventListener("updateend", async() => {
                count++;
                if (count < bufferList.length) await sourceBuffer.appendBuffer(bufferList[count]);
            });
        }
        // 加载文件
        function loadFile(src) {
            return new Promise((resolve, reject) => {
                let xhr = new XMLHttpRequest();
                xhr.open('GET', src);
                xhr.responseType = 'arraybuffer';
                xhr.addEventListener("load", (buffer) => {
                    resolve((buffer.target || buffer.target).response)
                });
                xhr.addEventListener("error", () => {
                    console.log("error:" + src);
                    reject();
                });
                xhr.addEventListener("abort", () => {
                    console.log("aborted:" + src);
                    reject();
                });
                xhr.send()
            });
        }
        load();
    </script>
</body>

</html>

三、效果

demo仅加载视频示例,未加载音频
在这里插入图片描述

视频仅做测试使用,未进行任何商业用途, 遮挡一下看到播放效果即可,如有侵权,联系作者删除。

四、问题

加密插件:
shaka package:3.4.0、3.3.0、3.2.1执行无反应,使用3.2.0可执行加密,但不支持clearkey

packager input=libx264.mp4,stream=video,output=encrypted_video.mp4 --keys key_id=00112233445566778899aabbccddeeff:key=ffeeddccbbaa99887766554433221100 --enable_raw_key_encryption --protection_systems "Widevine"

ffmpeg:不支持DRM的ckearkey加密

// ffmpeg -i test2.mp4 -vcodec copy -acodec copy -encryption_scheme cenc-aes-ctr -encryption_key 76a6c65c5ea762046bd749a2e632ccbb -encryption_kid a7e61c373e219033c21091fa607bf3b8 output.mp4
// ffmpeg -i test2.mp4 -c:v libx264 -c:a aac -f mp4 -encryption_scheme cenc-aes-ctr -key 76a6c65c5ea762046bd749a2e632ccbb -encryption_kid a7e61c373e219033c21091fa607bf3b8 encrypted_video.mp4

mp4Box:未实现

// MP4Box.exe -crypt drm.xml test2.mp4 -out tempv.mp4
// MP4Box.exe -crypt drm.xml test2.mp4 -out tempa.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 tempv.mp4#trackID=1:id=v1:period=p0 tempa.mp4#trackID=1:id=a1:period=p0

总结

踩坑路漫漫长@~@

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值