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 98 天前
正确的做法是转码时让 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
newbie111 OP @yuzo555 你好我知道这种官方标准加密方式的,直接 ffmpeg 就行,前端不需要任何处理,弊端就是任何人能拿到文件链接的情况下都能播放,不能够满足我这边的需求。
|
3
newbie111 OP ffmpeg 官方加密的方式,即使有防盗链,也可以通过批量下载的方式拿到可播放的文件。
|
4
dullwit 98 天前
DRM ?
|
5
LuckyLauncher 98 天前
这个是自己加密前端实现解密?那不就相当于没加密?
|
6
newbie111 OP @LuckyLauncher 不能这么说吧,玩 next.js 一把梭的时候各种数据库相关的配置不也是存放在前端项目的.env 里,只要解密 key 不暴露即可。可以理解为前后端约定好了一套加解密规则,后端加密数据,前端取到后按约定好的规则进行数据解密。
|
7
LuckyLauncher 98 天前
@newbie111 #6 你在说些啥?你把.env 放到前端让人可以访问到?你把地址放出来等一会你看看你数据库还在不在
|
8
LuckyLauncher 98 天前
|
9
dzdh 98 天前
通通加密后。你 m3u8 的解密密钥怎么下发。写死到网页里?通过接口获取?
还是仅仅防一下小白而已? |
10
dzdh 98 天前
感觉还是服务端做鉴权靠谱
|
11
helone 98 天前
100u 怕是有点少
|
12
wbrobot 98 天前
你这种就是请人定做一个 hls.js 嘛,获取到视频流之前前端解密一下,再拼成新的视频交给播放器播放。
你直接说定制 hls.js 就行了,什么算法都能实现,甚至不用全文件加密,只需要很短的 aes 随机替换 ts 中间的几个字节,盗播的人都播不了。 |
13
registerrr 98 天前
没盯上你的,用 ffmpeg 自带的加密也足够了。真盯上你的,只要你的前端有解密逻辑,有什么破不了的,都能破。防防小白而已
|
14
paradox8599 98 天前 via Android
@newbie111 不是吧,nextjs 的 env 只有 `NEXT_PUBLIC_` 开头的才会 1 暴露给前端呀
|
15
yb2313 98 天前
是我想的那种网站吗, 让我看看
|
16
qq78660651 98 天前
你想要实现的功能 和 alist 的 crypt 的文件加密功能相似呀,可以参考他的代码。
下载学习资料,加密后上传云盘,云盘存储的是加密后的文件,然后云盘播放时,实时解码观看,等于小带宽换大带宽。 相当于客户端负责加密和解密,文件在云端是加密存储的。 |
17
purringpal 98 天前 via iPhone
如果是特定用户才能看,你就把密钥分发给他们,至于前端加解密,聊胜于无吧
|
18
lyxxxh2 98 天前
@newbie111
"不也是存放在前端项目的.env 里,只要解密 key 不暴露即可" `next.js`我没用过,但听过,`next.js` 严格来说是后端。 跟 laravel 差不多, `.env`客户不知道。 如果我在模板引擎: ``` <script> key = {{ env('key') }} </script> ``` 客户浏览器可以看得到吧。 *** 或许你说我不定义,那 js 怎么拿 key 解密? 除非你有个登录,让用户绑定 key 。 |
19
SingeeKing 98 天前 via iPhone
这种我做过,基于 hls.js 改的,但是 100u 确实少了点
|
20
wen20 98 天前
.ts 都是绝对路径的话,可以试下。
|
21
ppddtt 98 天前
这样做没有意义,本质上还是本地解密,客户端包含密钥信息
|
22
dyllen 98 天前 1
@newbie111 你这话好多问题,前端项目的.env 文件并不在前端,那是在服务器保存的,并不会暴露。人家说的前端指浏览器端,不是你这前端项目。aes 加密,浏览器播放端要解密,必定需要密钥,别人就能拿到。
|
23
tool2dx 98 天前
还不如学 youtube ,直接把视频接口从 get 都改成 post 。
个人感觉,把后端鉴权做好就够了,保证走你自己加密的 js 那一套 web 流程。 |
24
EridanusSora 98 天前 via Android
视频加密的正确方法:上 DRM ,比如 widevine
|
25
easydou 98 天前 via Android
加密 m3u8 的话,也可以用国内的一些厂商产品,比如保利威,阿里云等。如果自己写的话,最好打包成 wasm ,这样破解难度高,解密速度也比直接 js 解密要快
|
26
fly9i 98 天前
一般常规做法是在 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 98 天前
@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 98 天前 via Android
用 ffmpeg 加密之后,得到 m3u8 文件,把其中的解密 key 加密,前端播放前解密,这样对方拿到 m3u8 也无法播放,不过问题是解密是在前端,对方研究代码也能找到破解的方法,代价是花费更多的时间
|
29
puzzle9 98 天前
m3u8
要不 你自己重新实现下解密流程 像那种视频网站一样 增加下破解难度而已 |
30
joewang1988 97 天前
做完了,怎么交付?
|
31
joewang1988 97 天前
收款账号信息
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 97 天前
还有个骚操作办法,先视频预处理对 rgb 通道进行异或,前端播放时根据 key 还原,速度非常快对性能影响很小,虽然没加密但是对方没 key 播放出来就是花屏
|
33
Oldletter 97 天前
|
34
BaiLinfeng 97 天前
这是想做啥黑科技
|
35
Ipsum 97 天前
@BaiLinfeng 我感觉是想放毛片在国内 cdn 上躲避审查。
|
36
BaiLinfeng 97 天前
@Ipsum 还可以这样玩的吗?有 demo 吗,我看看,那么骚的操作
|
37
azhangbing 97 天前
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 97 天前
上午写完的,但联系不上,还能拿到 100u 么 [狗头]
Demo:[https://decipher-m3u8.yaavi.me/]( https://decipher-m3u8.yaavi.me/) |
39
aycclm 97 天前
你们真的是卷死了 [手动狗头]
100u 还真给做了 |
40
jjl19960910 97 天前 via Android
@Ipsum #35 hh 笑死
|
41
newbie111 OP 抱歉各位,我这里有 12 小时时差,现在是早上 6 点 46 ,刚起床,已经有好几个人加我,我在确认。
|
42
k9982874 97 天前 via Android
问问 ai 用 js 怎么 sha256 加密解密文件不就完事了。。
|
44
Yaavi 97 天前 1
|
45
newbie111 OP 抱歉 31 楼和其他加我的几位老哥,为节约大家时间,我优先加了在申请备注中提示“已按方案完成”的 Yaavi 老哥,验证后完美运行。31 楼老哥代码我刚跑了下,对比 Yaavi 老哥提供的代码,同样的 url ,会提示跨域,就没继续往下看了,感谢大家的帮忙,已转 yaavi 。
|
46
newbie111 OP 感谢 v 站,藏龙卧虎果然不虚。
|
47
joewang1988 97 天前
|
48
joewang1988 97 天前
@azhangbing 感谢
|
49
joewang1988 97 天前
@Yaavi 看你代码写得不错。方便留个 wx 不 有前端的活。200 刀一个。
|
50
pxiphx891 97 天前
我理解,解密密钥应该在这个 js 文件里,谁能教教我,怎么找到解密密钥?我打断点找了半天也没找着
https://decipher-m3u8.yaavi.me/assets/index-DCANJRc2.js |
51
GooMS 97 天前 via Android
掩耳盗铃
|
52
pxiphx891 97 天前
看了 github 源码,我知道了密钥是 1234567890ABCDEF ,谁能教教我,有什么方法能在只有混淆后代码的情况下,通过调试一步一步找到密钥?
|
53
07aPzknB16ui9Cp3 97 天前
@Yaavi 想提个问,简单看了一下代码,看起来 decryptTSAES 是拿去的 fetch 下来的全量 ArrayBuffer ,但是我个人理解对于 TS 这样的视频文件不是流式处理不会出问题吗
|
54
07aPzknB16ui9Cp3 97 天前
没事了,突然意识到是 m3u8 ,那么本身就是分片
|
55
Yaavi 97 天前
@joewang1988 没问题 -vsme- 含两头的 -
|
57
caola 97 天前
解密的密钥 key 放在 wasm 里面,多弄几个变量混淆一下密钥,还可以加入其他的参数进行校验(例如:服务器给出一个加密后的时间戳,wasm 解密这个参数后校验这个时间在一定的范围内就允许解密操作),然后再编译成二进制。一般能破解的概率小很多了。
js 把加密的数据和参数传入 wasm 解密后的数据反回给 js 。 |