
1.通过以下命令对影片的明文 m3u8 和 ts 文件进行加密( aes-ecb ):
openssl enc -aes-128-ecb -in decrypt/index.m3u8 -out encrypt/index.m3u8 -K $(echo -n "你自己任意指定的 16 位加密 key" | xxd -p) openssl enc -aes-128-ecb -in decrypt/001.ts -out encrypt/001.ts -K $(echo -n "你自己任意指定的 16 位加密 key" | xxd -p) 2.前端页面请求到 m3u8 播放链接时解密文件并播放。
这里我上传了一个不到 10 秒的未加密素材供测试,当然你也可以自己找个 mp4 文件切片成 m3u8 然后本地测试解密播放,先提前谢过各位前端老哥了,一旦成功解密播放,立即支付 100u 表达谢意。我的理解是 hls.js 或 crypto.js 之类的库来解密,但我不是专业前端所以改起来有困难。。。 https://drive.google.com/drive/folders/1QPD_E6C34tND0hICVrWIemxjRL-RLnad?usp=sharing
1 yuzo555 2024-09-11 07:16:48 +08:00 正确的做法是转码时让 ffmpeg 自动处理,而不是转码后自己去处理。以下方法来自 GPT-4o: 要使用 FFmpeg 将 MP4 格式的视频转码为 HLS 流并使用 HLS 标准加密( AES-128 ),你可以使用以下命令: ffmpeg -i input.mp4 \ -c:v libx264 -c:a aac \ -hls_time 10 \ -hls_key_info_file key_info_file.txt \ -hls_playlist_type vod \ output.m3u8 在使用这个命令之前,你需要准备一个 key_info_file.txt 文件,其中包含加密密钥信息。该文件的格式如下: key_uri key_file_path key_iv key_uri:在播放列表中引用密钥的 URI (通常是密钥的 URL )。 key_file_path:包含实际密钥的文件路径。 key_iv:初始化向量( IV ),如果不指定,FFmpeg 会自动生成。 例如: https://example.com/keys/key.bin ./key.bin 0123456789abcdef0123456789abcdef 你还需要生成一个 16 字节的密钥,并将其保存到 key.bin 文件中。可以使用以下命令生成随机密钥: openssl rand 16 > key.bin 请确保 key_info_file.txt 和 key.bin 文件的路径正确,并根据你的需求调整 FFmpeg 命令中的参数。 |
2 18bili OP @yuzo555 你好我知道这种官方标准加密方式的,直接 ffmpeg 就行,前端不需要任何处理,弊端就是任何人能拿到文件链接的情况下都能播放,不能够满足我这边的需求。 |
3 18bili OP ffmpeg 官方加密的方式,即使有防盗链,也可以通过批量下载的方式拿到可播放的文件。 |
4 dullwit 2024-09-11 08:10:58 +08:00 DRM ? |
5 LuckyLauncher 2024-09-11 08:40:30 +08:00 这个是自己加密前端实现解密?那不就相当于没加密? |
6 18bili OP @LuckyLauncher 不能这么说吧,玩 next.js 一把梭的时候各种数据库相关的配置不也是存放在前端项目的.env 里,只要解密 key 不暴露即可。可以理解为前后端约定好了一套加解密规则,后端加密数据,前端取到后按约定好的规则进行数据解密。 |
7 LuckyLauncher 2024-09-11 09:03:03 +08:00 @newbie111 #6 你在说些啥?你把.env 放到前端让人可以访问到?你把地址放出来等一会你看看你数据库还在不在 |
8 LuckyLauncher 2024-09-11 09:05:01 +08:00 |
9 dzdh 2024-09-11 09:06:51 +08:00 通通加密后。你 m3u8 的解密密钥怎么下发。写死到网页里?通过接口获取? 还是仅仅防一下小白而已? |
10 dzdh 2024-09-11 09:10:18 +08:00 感觉还是服务端做鉴权靠谱 |
11 helone 2024-09-11 09:10:51 +08:00 100u 怕是有点少 |
12 wbrobot 2024-09-11 09:13:27 +08:00 你这种就是请人定做一个 hls.js 嘛,获取到视频流之前前端解密一下,再拼成新的视频交给播放器播放。 你直接说定制 hls.js 就行了,什么算法都能实现,甚至不用全文件加密,只需要很短的 aes 随机替换 ts 中间的几个字节,盗播的人都播不了。 |
13 registerrr 2024-09-11 09:17:03 +08:00 没盯上你的,用 ffmpeg 自带的加密也足够了。真盯上你的,只要你的前端有解密逻辑,有什么破不了的,都能破。防防小白而已 |
14 paradox8599 2024-09-11 09:26:47 +08:00 via Android @newbie111 不是吧,nextjs 的 env 只有 `NEXT_PUBLIC_` 开头的才会 1 暴露给前端呀 |
15 yb2313 2024-09-11 09:28:11 +08:00 是我想的那种网站吗, 让我看看 |
16 qq78660651 2024-09-11 09:32:34 +08:00 你想要实现的功能 和 alist 的 crypt 的文件加密功能相似呀,可以参考他的代码。 下载学习资料,加密后上传云盘,云盘存储的是加密后的文件,然后云盘播放时,实时解码观看,等于小带宽换大带宽。 相当于客户端负责加密和解密,文件在云端是加密存储的。 |
17 purringpal 2024-09-11 09:34:00 +08:00 via iPhone 如果是特定用户才能看,你就把密钥分发给他们,至于前端加解密,聊胜于无吧 |
18 lyxxxh2 2024-09-11 09:34:07 +08:00 @newbie111 "不也是存放在前端项目的.env 里,只要解密 key 不暴露即可" `next.js`我没用过,但听过,`next.js` 严格来说是后端。 跟 laravel 差不多, `.env`客户不知道。 如果我在模板引擎: ``` <script> key = {{ env('key') }} </script> ``` 客户浏览器可以看得到吧。 *** 或许你说我不定义,那 js 怎么拿 key 解密? 除非你有个登录,让用户绑定 key 。 |
19 SingeeKing PRO 这种我做过,基于 hls.js 改的,但是 100u 确实少了点 |
20 wen20 2024-09-11 09:39:42 +08:00 .ts 都是绝对路径的话,可以试下。 |
21 ppddtt 2024-09-11 10:21:21 +08:00 这样做没有意义,本质上还是本地解密,客户端包含密钥信息 |
22 dyllen 2024-09-11 10:31:28 +08:00 @newbie111 你这话好多问题,前端项目的.env 文件并不在前端,那是在服务器保存的,并不会暴露。人家说的前端指浏览器端,不是你这前端项目。aes 加密,浏览器播放端要解密,必定需要密钥,别人就能拿到。 |
23 tool2dx 2024-09-11 10:35:46 +08:00 还不如学 youtube ,直接把视频接口从 get 都改成 post 。 个人感觉,把后端鉴权做好就够了,保证走你自己加密的 js 那一套 web 流程。 |
24 EridanusSora 2024-09-11 11:03:45 +08:00 via Android 视频加密的正确方法:上 DRM ,比如 widevine |
25 easydou 2024-09-11 11:10:30 +08:00 via Android 加密 m3u8 的话,也可以用国内的一些厂商产品,比如保利威,阿里云等。如果自己写的话,最好打包成 wasm ,这样破解难度高,解密速度也比直接 js 解密要快 |
26 fly9i 2024-09-11 11:21:16 +08:00 一般常规做法是在 m3u8 不加密,ts 文件加密,每个 ts 文件密钥可以不一样,加密一般是用 aes-128-cbc 。 m3u8 中配置一条 #EXT-X-KEY:METHOD=AES-128,URI="https://example.com/path/to/key",IV=0xabcdef0123456789abcdef0123456789 类似这样的数据,hls.js 每次请求 ts 前会先先请求密钥,ts 解密也是库里自动了。 |
27 thinkingbullet 2024-09-11 11:24:11 +08:00 @yuzo555 ffmpeg -i video.mp4 -codec:v libx264 -codec:a aac -strict -2 -f hls -hls_key_info_file key_info.txt -hls_segment_type mpegts -hls_encryption_algorithm AES-128 -hls_key_url http://example.com/path/to/key playlist.m3u8 gpt3.5 的答案是这个,不知道谁真谁假 |
28 abusizhishen 2024-09-11 11:51:15 +08:00 via Android 用 ffmpeg 加密之后,得到 m3u8 文件,把其中的解密 key 加密,前端播放前解密,这样对方拿到 m3u8 也无法播放,不过问题是解密是在前端,对方研究代码也能找到破解的方法,代价是花费更多的时间 |
29 puzzle9 2024-09-11 11:51:56 +08:00 m3u8 要不 你自己重新实现下解密流程 像那种视频网站一样 增加下破解难度而已 |
30 joewang1988 2024-09-11 15:42:29 +08:00 做完了,怎么交付? |
31 joewang1988 2024-09-11 16:15:30 +08:00 收款账号信息 Address :TD8j8Z76JP4t9PZPdtspZVbgwnFt2FDAUf Token:USDT Network:Tron 原理 使用 hlsjs 的 custom loader 在请求到 m3u8 和 ts 文件时,分别进行相对应的解密操作。 以下是完整代码 <html> <head> <title>Hls.js demo - basic usage</title> </head> <body> <script src="https://cdnjs.cloudflare.com/ajax/libs/hls.js/1.5.15/hls.min.js"></script> <script src="./crypto-js.min.js"></script> <center> <h1>Hls.js demo - basic usage</h1> <video height="600" id="video" controls></video> </center> <script> async function process(playlist) { const key = '1111111111111111'; const keyBytes = CryptoJS.enc.Utf8.parse(key); console.log(playlist); const byteArray = new Uint8Array(playlist); const wordArray = CryptoJS.lib.WordArray.create(byteArray); // Decrypt the data const decrypted = CryptoJS.AES.decrypt( { ciphertext: wordArray }, keyBytes, { mode: CryptoJS.mode.ECB, padding: CryptoJS.pad.Pkcs7 } ); console.log(decrypted) // Convert decrypted data to text const decryptedText = decrypted.toString(CryptoJS.enc.Utf8); console.log(decryptedText); return decryptedText; } // 转换 decrypted 为 ArrayBuffer function wordArrayToArrayBuffer(wordArray) { // 计算需要的长度 const length = wordArray.sigBytes; const words = wordArray.words; const bytes = new Uint8Array(length); // 将 wordArray 的每个 word 转换为 byte for (let i = 0; i < length; i++) { bytes[i] = (words[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xFF; } return bytes.buffer; } async function processData(data) { console.log(data); const key = '1111111111111111'; const keyBytes = CryptoJS.enc.Utf8.parse(key); console.log(data); const byteArray = new Uint8Array(data); const wordArray = CryptoJS.lib.WordArray.create(byteArray); // Decrypt the data const decrypted = CryptoJS.AES.decrypt( { ciphertext: wordArray }, keyBytes, { mode: CryptoJS.mode.ECB, padding: CryptoJS.pad.Pkcs7 } ); console.log(decrypted) const arrayBuffer = wordArrayToArrayBuffer(decrypted); return arrayBuffer; } class CustomLoader extends Hls.DefaultConfig.loader { constructor(config) { super(config); var load = this.load.bind(this); //var xhrSetup = this.xhrSetup.bind(this); this.load = function (context, config, callbacks) { context.respOnseType= 'arraybuffer'; if (context.type == 'manifest') { var OnSuccess= callbacks.onSuccess; callbacks.OnSuccess= function (response, stats, context, networkDetails) { console.log(networkDetails) process(response.data) .then(data => { response.data = data; onSuccess(response, stats, context); }) .catch(err => { console.error(err); }) }; } else { const OnSuccess= callbacks.onSuccess; callbacks.OnSuccess= function (response, stats, context) { processData(response.data) .then(data => { response.data = data; onSuccess(response, stats, context); }) .catch(err => { console.error(err); }) }; } load(context, config, callbacks); }; } } // Create the Hls instance with the custom fetch loader const myHls = new Hls({ //debug: true, loader: CustomLoader, xhrSetup: function (xhr, url) { xhr.open('GET', url, true); xhr.setRequestHeader('id', 1) xhr.setRequestHeader('token', 456) xhr.respOnseType= '111'; // do send cookies console.log("XXXXXXX") } }); const video = document.getElementById('video'); myHls.loadSource('./index.e.m3u8'); myHls.attachMedia(video); myHls.on(Hls.Events.MANIFEST_PARSED, function () { video.play(); }); myHls.on(Hls.Events.ERROR, function (event, data) { if (data.fatal) { console.error('HLS.js Error:', data); } }); </script> </body> </html> |
32 rekulas 2024-09-11 16:31:32 +08:00 还有个骚操作办法,先视频预处理对 rgb 通道进行异或,前端播放时根据 key 还原,速度非常快对性能影响很小,虽然没加密但是对方没 key 播放出来就是花屏 |
33 Oldletter 2024-09-11 16:54:22 +08:00 |
34 BaiLinfeng 2024-09-11 17:01:42 +08:00 这是想做啥黑科技 |
35 Ipsum 2024-09-11 17:07:52 +08:00 @BaiLinfeng 我感觉是想放毛片在国内 cdn 上躲避审查。 |
36 BaiLinfeng 2024-09-11 17:11:26 +08:00 @Ipsum 还可以这样玩的吗?有 demo 吗,我看看,那么骚的操作 |
37 azhangbing 2024-09-11 17:30:13 +08:00 31 楼正确 你只需要重写 class CustomFragmentLoader extends Hls.DefaultConfig.loader 的 load 方法就行了 load(context, config, callbacks) { const OnSuccessOriginal= callbacks.onSuccess; callbacks.OnSuccess= (response, stats, context) => { if (context.frag && context.frag.url && context.frag.url.includes('.ts')) { const encryptedData = new Uint8Array(response.data); const filenameBase = this.extractFilename(context.frag.url); const decryptedData = xorDecrypt(encryptedData, this.keyBytes); response.data = decryptedData.buffer; } onSuccessOriginal(response, stats, context); }; super.load(context, config, callbacks); } |
38 Yaavi 2024-09-11 18:40:21 +08:00 上午写完的,但联系不上,还能拿到 100u 么 [狗头] Demo:[https://decipher-m3u8.yaavi.me/]( https://decipher-m3u8.yaavi.me/) |
39 aycclm 2024-09-11 18:44:43 +08:00 你们真的是卷死了 [手动狗头] 100u 还真给做了 |
40 jjl19960910 2024-09-11 18:47:24 +08:00 via Android @Ipsum #35 hh 笑死 |
41 18bili OP 抱歉各位,我这里有 12 小时时差,现在是早上 6 点 46 ,刚起床,已经有好几个人加我,我在确认。 |
42 k9982874 2024-09-11 18:50:40 +08:00 via Android 问问 ai 用 js 怎么 sha256 加密解密文件不就完事了。。 |
44 Yaavi 2024-09-11 19:35:14 +08:00 |
45 18bili OP 抱歉 31 楼和其他加我的几位老哥,为节约大家时间,我优先加了在申请备注中提示“已按方案完成”的 Yaavi 老哥,验证后完美运行。31 楼老哥代码我刚跑了下,对比 Yaavi 老哥提供的代码,同样的 url ,会提示跨域,就没继续往下看了,感谢大家的帮忙,已转 yaavi 。 |
46 18bili OP 感谢 v 站,藏龙卧虎果然不虚。 |
47 joewang1988 2024-09-11 20:02:44 +08:00 |
48 joewang1988 2024-09-11 20:03:05 +08:00 @azhangbing 感谢 |
49 joewang1988 2024-09-11 21:02:39 +08:00 @Yaavi 看你代码写得不错。方便留个 wx 不 有前端的活。200 刀一个。 |
50 pxiphx891 2024-09-11 21:15:28 +08:00 我理解,解密密钥应该在这个 js 文件里,谁能教教我,怎么找到解密密钥?我打断点找了半天也没找着 https://decipher-m3u8.yaavi.me/assets/index-DCANJRc2.js |
51 3IOhG7M0knRu5UlC 2024-09-11 21:21:47 +08:00 via Android 掩耳盗铃 |
52 pxiphx891 2024-09-11 21:23:33 +08:00 看了 github 源码,我知道了密钥是 1234567890ABCDEF ,谁能教教我,有什么方法能在只有混淆后代码的情况下,通过调试一步一步找到密钥? |
53 07aPzknB16ui9Cp3 2024-09-11 21:26:40 +08:00 @Yaavi 想提个问,简单看了一下代码,看起来 decryptTSAES 是拿去的 fetch 下来的全量 ArrayBuffer ,但是我个人理解对于 TS 这样的视频文件不是流式处理不会出问题吗 |
54 07aPzknB16ui9Cp3 2024-09-11 21:30:51 +08:00 没事了,突然意识到是 m3u8 ,那么本身就是分片 |
55 Yaavi 2024-09-11 21:31:00 +08:00 @joewang1988 没问题 -vsme- 含两头的 - |
57 caola 2024-09-12 02:18:00 +08:00 解密的密钥 key 放在 wasm 里面,多弄几个变量混淆一下密钥,还可以加入其他的参数进行校验(例如:服务器给出一个加密后的时间戳,wasm 解密这个参数后校验这个时间在一定的范围内就允许解密操作),然后再编译成二进制。一般能破解的概率小很多了。 js 把加密的数据和参数传入 wasm 解密后的数据反回给 js 。 |