第二章:解密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参数指定。例如:
python 复制代码
 #EXT-X-KEY:METHOD=AES-128,URI="https://example.com/keyfile"

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

python 复制代码
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参数指定,并且通常以十六进制字符串的形式给出。例如:

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

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

python 复制代码
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库来实现解密。
python 复制代码
pip install Crypto #安装方法
  1. 合并TS文件:解密后的TS文件可以被合并成一个大的TS文件,然后转换为MP4格式,以便于播放。

3、示例代码

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

python 复制代码
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获取

python 复制代码
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,并将其存储在安全的地方,以便解密时可以使用。

相关推荐
咚咚王者10 分钟前
人工智能之编程进阶 Python高级:第十一章 过渡项目
开发语言·人工智能·python
A尘埃1 小时前
大模型应用python+Java后端+Vue前端的整合
java·前端·python
A尘埃1 小时前
LLM大模型评估攻略
开发语言·python
一晌小贪欢1 小时前
【Python办公】处理 CSV和Excel 文件操作指南
开发语言·python·excel·excel操作·python办公·csv操作
檀越剑指大厂2 小时前
【Python系列】fastapi和flask中的阻塞问题
python·flask·fastapi
YoungHong19923 小时前
【Python进阶】告别繁琐Debug!Loguru一键输出异常日志与变量值
python·debug·异常处理·日志·loguru·log·logger
AiXed4 小时前
PC微信协议之nid算法
python·网络协议·算法·微信
小李哥哥4 小时前
基于数据的人工智能建模流程及源码示例
python
APIshop5 小时前
实战解析:苏宁易购 item_search 按关键字搜索商品API接口
开发语言·chrome·python
蓝桉~MLGT5 小时前
Python学习历程——Python面向对象编程详解
开发语言·python·学习