自学内容网 自学内容网

第二章:解密HLS流——揭秘m3u8加密与解密

第二章:基于DrissionPage的M3U8文件解密与视频合并技术详解

背景

解决m3u8文件中加密视频的问题,我们首先需要了解加密的原理,然后根据原理来解密视频。

1、加密原理

m3u8文件中的视频内容可以被加密,以保护版权和防止未授权访问。在HTTP Live Streaming (HLS)流中,有两种主要的加密方法:

  1. AES-128加密:这是HLS中最常见的加密方法。在这种模式下,每个.ts文件或.m3u8播放列表文件都可以被单独加密。加密的.ts文件通常有一个.ivf(初始化向量文件)扩展名,但技术上它仍然是一个.ts文件。在M3U8文件中,#EXT-X-KEY标签用于指定密钥信息,包括加密方法、获取密钥的URL和初始化向量(IV)。

  2. 样本加密(Sample Encryption):样本加密是对视频和音频样本进行加密,而不是对整个文件进行加密。这种方法可以减少延迟,因为它允许播放器在下载密钥后立即开始解密和播放媒体流。

  3. DRM加密(Digital Rights Management):除了AES-128加密外,HLS还支持DRM加密,如PlayReady和Widevine。这些DRM解决方案提供了更高级的保护,但也需要客户端支持相应的DRM客户端。

2、解密方法

  1. 获取密钥:首先,需要从M3U8文件中指定的URL下载密钥。这通常通过#EXT-X-KEY标签中的URI字段指定。在m3u8文件中,加密信息是通过#EXT-X-KEY标签来存储的,这个标签包含了密钥的URL和初始化向量(IV)。以下是具体的存储方式:
    (1)密钥的URL(URI):这是密钥存储的位置,客户端需要从这个URL下载密钥以用于解密视频流。在#EXT-X-KEY标签中,密钥的URL通过URI参数指定。例如:
 #EXT-X-KEY:METHOD=AES-128,URI="https://example.com/keyfile"

这里METHOD指定了加密方法,这里是AES-128,而URI指定了密钥文件的URL。

import re

# 读取爬到的m3u8文件中通过正则匹配到对应的URL
def parse_m3u8_text(m3u8_text):
    m3u8_text = m3u8_text.split()
    encode_info = [line for line in m3u8_text if line.startswith('#EXT-X-KEY:')][0]
    pattern = r"#EXT-X-KEY:METHOD=(.*),URI=\"(.*)\""
    match = re.search(pattern, encode_info)
    if match:
        method = match.group(1)
        key_url = match.group(2)
    else:
        raise '解析失败'
    return method, key_url
    
if __name__ == "__main__":
method, key_url = parse_m3u8_text(m3u8_text)

(2)初始化向量(IV):IV用于AES加密算法,以确保即使相同的密钥加密相同的数据,每次加密的结果也会不同。在#EXT-X-KEY标签中,IV通过IV参数指定,并且通常以十六进制字符串的形式给出。例如:

  #EXT-X-KEY:METHOD=AES-128,URI="https://example.com/keyfile",IV=0x1234567890abcdef1234567890abcdef

这里IV参数后面跟着的是以0x开头的十六进制数,表示IV的值。

def parse_m3u8_text(m3u8_text):
    m3u8_text = m3u8_text.split()
    encode_info = [line for line in m3u8_text if line.startswith('#EXT-X-KEY:')][0]
    pattern = r"#EXT-X-KEY:METHOD=(.*),URI=\"(.*)\",IV=(.*)"
    match = re.search(pattern, encode_info)
    if match:
        method = match.group(1)
        key_url = match.group(2)
        iv = match.group(3)
    else:
        raise '解析失败'
    return method, key_url, iv

在实际应用中,客户端播放器会解析m3u8文件中的#EXT-X-KEY标签,提取出密钥的URL和IV,然后通过网络请求下载密钥文件,并使用这个密钥和IV对视频流进行解密。这样,只有拥有正确密钥和IV的客户端才能播放视频内容,从而保护了视频内容的版权和安全性。

  1. 解密TS文件:使用下载的密钥URL和IV(初始化向量),对每个加密的TS文件进行解密。这通常使用AES-128-CBC模式进行解密。例如,可以使用Python的Crypto.Cipher库来实现解密。
pip install Crypto #安装方法
  1. 合并TS文件:解密后的TS文件可以被合并成一个大的TS文件,然后转换为MP4格式,以便于播放。

3、示例代码

以下是一个简单的Python代码示例,展示了如何解密m3u8文件中的加密TS文件:

from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad

# 解密函数,基于key和iv
def decrypt(data, key, iv):
    cipher = AES.new(key, AES.MODE_CBC, iv)
    dec_data = unpad(cipher.decrypt(data), AES.block_size)
    return dec_data

if __name__ == '__main__':
    key = bytes.fromhex('5931636472715A5A35446E5441614F50')  # 十六进制密钥
    iv = bytes.fromhex('00000000000000000000000000000000')  # 十六进制IV
    # 读取本地加密ts文件
    with open('1.ts', 'rb') as f:
        enc_ts = f.read()
        # 解密ts
        dec_ts = decrypt(enc_ts, key, iv)
        # 将解密后的ts保存
        with open('dec.ts', 'wb') as ff:
            ff.write(dec_ts)

执行上述代码后,解密的TS文件就可以正常播放了。后续可以增加代码,实现从m3u8文件读取所有的TS文件,进行批量解密,然后合并成一个大的TS文件,最后再转为MP4格式。

4、完整代码

其中的key.key就是m3u8文件中的URL的值,有的也是完整链接,如果没有完整链接,可以通过浏览器检查点network获取对应的header里的Request URL获取

import asyncio
import aiofiles
import requests
from Crypto.Cipher import AES
import os

def get_key(url):
    """通过m3u8文件中的加密url获取对应的key"""
    response = requests.get(url)
    if response.status_code == 200:
        return response
    else:
        return None

async def dec_ts(name,key):
    """
    解密每一个ts文件然后原地改名保存
    :param name: ts文件名
    :param key: 加密文件的key,是通过函数get_key获取
    :return:
    """
    save_dir = r"video"
    os.makedirs(save_dir, exist_ok=True)
    aes = AES.new(key=key,IV=b"0000000000000000",mode=AES.MODE_CBC)
    # 先读ts再解密后保存为ts
    async with aiofiles.open(os.path.join(save_dir,name),'rb') as f1,\
            aiofiles.open(os.path.join(save_dir,name),'wb') as f2:
            bs = await f1.read() # 读取文件
            await f2.write(aes.decrypt(bs)) # 解密好的内容写入文件
    print(f"{name} 解密完成!")

async def aio_decrypt(key):
    # 解密
    tasks = []
    async with aiofiles.open("playlist.m3u8","r",encoding="utf-8") as f:
        async for line in f:
            if line.startswith("#"):
                continue
            line = line.strip()
            # 开始创建异步任务
            task = asyncio.create_task(dec_ts(line,key))
            tasks.append(task)
        await asyncio.wait(tasks)


# 主函数,用于启动异步事件循环
async def main():
    key = get_key(r"http://xxxxx/xxxxx/key.key") # 其中的key.key就是m3u8文件中的URL的值,有的也是完整链接,如果没有完整链接,可以通过浏览器检查点network获取对应的header里的Request URL获取
    await aio_decrypt(key)

# 运行主函数
if __name__ == "__main__":
    asyncio.run(main())


5、扩展

在上述代码片段中,IV=b"0000000000000000" 是初始化向量(Initialization Vector)的值。初始化向量(IV)是用于AES加密算法中的一个额外的输入参数,与密钥(key)一起使用,以确保即使相同的数据块多次加密,结果也会因为不同的IV而不同。这增加了加密的安全性,因为相同的明文块不会产生相同的密文块。
IV被硬编码为一个固定的值 b"0000000000000000",这是一个16字节(128位)的全零值。使用全零IV的做法通常不推荐,因为它会降低加密的安全性,因为如果多个数据块使用相同的密钥和IV进行加密,攻击者可能会利用这个模式来发起攻击。
在实际应用中,IV应该是随机生成的,并且对于每个加密的数据块都是唯一的。这样可以确保即使明文相同,密文也会因为不同的IV而不同,从而提供更强的安全性。

如果你正在处理一个实际的加密和解密任务,你应该确保:

  1. IV是随机生成的,并且每次加密时都是唯一的。
  2. IV与密文一起传输或存储,以便解密时可以使用相同的IV。
  3. IV不应该硬编码在代码中,以避免安全风险。

在处理敏感数据时,这种做法是不安全的,应该避免。正确的做法是在每次加密时生成一个新的随机IV,并将其存储在安全的地方,以便解密时可以使用。


原文地址:https://blog.csdn.net/weixin_43687366/article/details/143867496

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!