V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
newbie111
V2EX  ›  React

100u 有偿请前端老哥实现解密播放 m3u8 文件

  •  
  •   newbie111 · 101 天前 · 6316 次点击
    这是一个创建于 101 天前的主题,其中的信息可能已经有所发展或是发生改变。

    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 条附言  ·  101 天前
    当然,上面列的步骤是方便兄弟们理解加解密过程,我这边也有现成的已经加密好的文件可直接用于本地解密播放测试,愿意试试的老哥可以联系我,小飞机:@niupi66666 ,VX:aW1uZXdiaWU2NjY=( base64 )
    57 条回复
    yuzo555
        1
    yuzo555  
       101 天前
    正确的做法是转码时让 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 命令中的参数。
    newbie111
        2
    newbie111  
    OP
       101 天前
    @yuzo555 你好我知道这种官方标准加密方式的,直接 ffmpeg 就行,前端不需要任何处理,弊端就是任何人能拿到文件链接的情况下都能播放,不能够满足我这边的需求。
    newbie111
        3
    newbie111  
    OP
       101 天前
    ffmpeg 官方加密的方式,即使有防盗链,也可以通过批量下载的方式拿到可播放的文件。
    dullwit
        4
    dullwit  
       101 天前
    DRM ?
    LuckyLauncher
        5
    LuckyLauncher  
       101 天前
    这个是自己加密前端实现解密?那不就相当于没加密?
    newbie111
        6
    newbie111  
    OP
       101 天前
    @LuckyLauncher 不能这么说吧,玩 next.js 一把梭的时候各种数据库相关的配置不也是存放在前端项目的.env 里,只要解密 key 不暴露即可。可以理解为前后端约定好了一套加解密规则,后端加密数据,前端取到后按约定好的规则进行数据解密。
    LuckyLauncher
        7
    LuckyLauncher  
       101 天前
    @newbie111 #6 你在说些啥?你把.env 放到前端让人可以访问到?你把地址放出来等一会你看看你数据库还在不在
    LuckyLauncher
        8
    LuckyLauncher  
       101 天前
    @newbie111 #6
    “只要解密 key 不暴露即可”
    “前端取到后按约定好的规则进行数据解密”
    你就说你的前端解密需不需要 key
    dzdh
        9
    dzdh  
       101 天前
    通通加密后。你 m3u8 的解密密钥怎么下发。写死到网页里?通过接口获取?

    还是仅仅防一下小白而已?
    dzdh
        10
    dzdh  
       101 天前
    感觉还是服务端做鉴权靠谱
    helone
        11
    helone  
       101 天前
    100u 怕是有点少
    wbrobot
        12
    wbrobot  
       101 天前
    你这种就是请人定做一个 hls.js 嘛,获取到视频流之前前端解密一下,再拼成新的视频交给播放器播放。
    你直接说定制 hls.js 就行了,什么算法都能实现,甚至不用全文件加密,只需要很短的 aes 随机替换 ts 中间的几个字节,盗播的人都播不了。
    registerrr
        13
    registerrr  
       101 天前
    没盯上你的,用 ffmpeg 自带的加密也足够了。真盯上你的,只要你的前端有解密逻辑,有什么破不了的,都能破。防防小白而已
    paradox8599
        14
    paradox8599  
       101 天前 via Android
    @newbie111 不是吧,nextjs 的 env 只有 `NEXT_PUBLIC_` 开头的才会 1 暴露给前端呀
    yb2313
        15
    yb2313  
       101 天前
    是我想的那种网站吗, 让我看看
    qq78660651
        16
    qq78660651  
       101 天前
    你想要实现的功能 和 alist 的 crypt 的文件加密功能相似呀,可以参考他的代码。
    下载学习资料,加密后上传云盘,云盘存储的是加密后的文件,然后云盘播放时,实时解码观看,等于小带宽换大带宽。
    相当于客户端负责加密和解密,文件在云端是加密存储的。
    purringpal
        17
    purringpal  
       101 天前 via iPhone
    如果是特定用户才能看,你就把密钥分发给他们,至于前端加解密,聊胜于无吧
    lyxxxh2
        18
    lyxxxh2  
       101 天前
    @newbie111

    "不也是存放在前端项目的.env 里,只要解密 key 不暴露即可"
    `next.js`我没用过,但听过,`next.js` 严格来说是后端。

    跟 laravel 差不多, `.env`客户不知道。
    如果我在模板引擎:
    ```
    <script>
    key = {{ env('key') }}
    </script>
    ```
    客户浏览器可以看得到吧。
    ***
    或许你说我不定义,那 js 怎么拿 key 解密?
    除非你有个登录,让用户绑定 key 。
    SingeeKing
        19
    SingeeKing  
       101 天前 via iPhone
    这种我做过,基于 hls.js 改的,但是 100u 确实少了点
    wen20
        20
    wen20  
       101 天前
    .ts 都是绝对路径的话,可以试下。
    ppddtt
        21
    ppddtt  
       101 天前
    这样做没有意义,本质上还是本地解密,客户端包含密钥信息
    dyllen
        22
    dyllen  
       101 天前   ❤️ 1
    @newbie111 你这话好多问题,前端项目的.env 文件并不在前端,那是在服务器保存的,并不会暴露。人家说的前端指浏览器端,不是你这前端项目。aes 加密,浏览器播放端要解密,必定需要密钥,别人就能拿到。
    tool2dx
        23
    tool2dx  
       101 天前
    还不如学 youtube ,直接把视频接口从 get 都改成 post 。

    个人感觉,把后端鉴权做好就够了,保证走你自己加密的 js 那一套 web 流程。
    EridanusSora
        24
    EridanusSora  
       101 天前 via Android
    视频加密的正确方法:上 DRM ,比如 widevine
    easydou
        25
    easydou  
       101 天前 via Android
    加密 m3u8 的话,也可以用国内的一些厂商产品,比如保利威,阿里云等。如果自己写的话,最好打包成 wasm ,这样破解难度高,解密速度也比直接 js 解密要快
    fly9i
        26
    fly9i  
       101 天前
    一般常规做法是在 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 解密也是库里自动了。
    thinkingbullet
        27
    thinkingbullet  
       101 天前
    @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 的答案是这个,不知道谁真谁假
    abusizhishen
        28
    abusizhishen  
       101 天前 via Android
    用 ffmpeg 加密之后,得到 m3u8 文件,把其中的解密 key 加密,前端播放前解密,这样对方拿到 m3u8 也无法播放,不过问题是解密是在前端,对方研究代码也能找到破解的方法,代价是花费更多的时间
    puzzle9
        29
    puzzle9  
       101 天前
    m3u8
    要不 你自己重新实现下解密流程 像那种视频网站一样
    增加下破解难度而已
    joewang1988
        30
    joewang1988  
       101 天前
    做完了,怎么交付?
    joewang1988
        31
    joewang1988  
       101 天前
    收款账号信息

    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>
    rekulas
        32
    rekulas  
       101 天前
    还有个骚操作办法,先视频预处理对 rgb 通道进行异或,前端播放时根据 key 还原,速度非常快对性能影响很小,虽然没加密但是对方没 key 播放出来就是花屏
    Oldletter
        33
    Oldletter  
       101 天前
    BaiLinfeng
        34
    BaiLinfeng  
       101 天前
    这是想做啥黑科技
    Ipsum
        35
    Ipsum  
       101 天前
    @BaiLinfeng 我感觉是想放毛片在国内 cdn 上躲避审查。
    BaiLinfeng
        36
    BaiLinfeng  
       101 天前
    @Ipsum 还可以这样玩的吗?有 demo 吗,我看看,那么骚的操作
    azhangbing
        37
    azhangbing  
       101 天前
    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);
    }
    Yaavi
        38
    Yaavi  
       101 天前
    上午写完的,但联系不上,还能拿到 100u 么 [狗头]
    Demo:[https://decipher-m3u8.yaavi.me/]( https://decipher-m3u8.yaavi.me/)
    aycclm
        39
    aycclm  
       101 天前
    你们真的是卷死了 [手动狗头]
    100u 还真给做了
    jjl19960910
        40
    jjl19960910  
       101 天前 via Android
    @Ipsum #35 hh 笑死
    newbie111
        41
    newbie111  
    OP
       101 天前
    抱歉各位,我这里有 12 小时时差,现在是早上 6 点 46 ,刚起床,已经有好几个人加我,我在确认。
    k9982874
        42
    k9982874  
       101 天前 via Android
    问问 ai 用 js 怎么 sha256 加密解密文件不就完事了。。
    Yaavi
        43
    Yaavi  
       101 天前
    @newbie111 已收到 100u ,感谢老哥~
    Yaavi
        44
    Yaavi  
       101 天前   ❤️ 1
    newbie111
        45
    newbie111  
    OP
       101 天前
    抱歉 31 楼和其他加我的几位老哥,为节约大家时间,我优先加了在申请备注中提示“已按方案完成”的 Yaavi 老哥,验证后完美运行。31 楼老哥代码我刚跑了下,对比 Yaavi 老哥提供的代码,同样的 url ,会提示跨域,就没继续往下看了,感谢大家的帮忙,已转 yaavi 。
    newbie111
        46
    newbie111  
    OP
       101 天前
    感谢 v 站,藏龙卧虎果然不虚。
    joewang1988
        47
    joewang1988  
       101 天前
    @newbie111

    会提示跨域 --- 你把 m3u8 文件和 ts 文件放在同一个 domain 下就不会有跨域问题了。

    我贴代码也是做个社会性测试。

    呵呵 果然是不公平啊
    joewang1988
        48
    joewang1988  
       101 天前
    @azhangbing 感谢
    joewang1988
        49
    joewang1988  
       101 天前
    @Yaavi 看你代码写得不错。方便留个 wx 不 有前端的活。200 刀一个。
    pxiphx891
        50
    pxiphx891  
       101 天前
    我理解,解密密钥应该在这个 js 文件里,谁能教教我,怎么找到解密密钥?我打断点找了半天也没找着

    https://decipher-m3u8.yaavi.me/assets/index-DCANJRc2.js
    GooMS
        51
    GooMS  
       101 天前 via Android
    掩耳盗铃
    pxiphx891
        52
    pxiphx891  
       101 天前
    看了 github 源码,我知道了密钥是 1234567890ABCDEF ,谁能教教我,有什么方法能在只有混淆后代码的情况下,通过调试一步一步找到密钥?
    07aPzknB16ui9Cp3
        53
    07aPzknB16ui9Cp3  
       101 天前
    @Yaavi 想提个问,简单看了一下代码,看起来 decryptTSAES 是拿去的 fetch 下来的全量 ArrayBuffer ,但是我个人理解对于 TS 这样的视频文件不是流式处理不会出问题吗
    07aPzknB16ui9Cp3
        54
    07aPzknB16ui9Cp3  
       101 天前
    没事了,突然意识到是 m3u8 ,那么本身就是分片
    Yaavi
        55
    Yaavi  
       101 天前
    @joewang1988 没问题 -vsme- 含两头的 -
    sunchuo
        56
    sunchuo  
       101 天前
    @Yaavi star 了。感谢。
    caola
        57
    caola  
       100 天前
    解密的密钥 key 放在 wasm 里面,多弄几个变量混淆一下密钥,还可以加入其他的参数进行校验(例如:服务器给出一个加密后的时间戳,wasm 解密这个参数后校验这个时间在一定的范围内就允许解密操作),然后再编译成二进制。一般能破解的概率小很多了。
    js 把加密的数据和参数传入 wasm 解密后的数据反回给 js 。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2779 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 25ms · UTC 13:49 · PVG 21:49 · LAX 05:49 · JFK 08:49
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.