第二章:基于DrissionPage的M3U8文件解密与视频合并技术详解
背景
解决m3u8文件中加密视频的问题,我们首先需要了解加密的原理,然后根据原理来解密视频。
1、加密原理
m3u8文件中的视频内容可以被加密,以保护版权和防止未授权访问。在HTTP Live Streaming (HLS)流中,有两种主要的加密方法:
-
AES-128加密 :这是HLS中最常见的加密方法。在这种模式下,每个
.ts
文件或.m3u8
播放列表文件都可以被单独加密。加密的.ts
文件通常有一个.ivf
(初始化向量文件)扩展名,但技术上它仍然是一个.ts
文件。在M3U8文件中,#EXT-X-KEY
标签用于指定密钥信息,包括加密方法、获取密钥的URL和初始化向量(IV)。 -
样本加密(Sample Encryption):样本加密是对视频和音频样本进行加密,而不是对整个文件进行加密。这种方法可以减少延迟,因为它允许播放器在下载密钥后立即开始解密和播放媒体流。
-
DRM加密(Digital Rights Management):除了AES-128加密外,HLS还支持DRM加密,如PlayReady和Widevine。这些DRM解决方案提供了更高级的保护,但也需要客户端支持相应的DRM客户端。
2、解密方法
- 获取密钥 :首先,需要从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的客户端才能播放视频内容,从而保护了视频内容的版权和安全性。
- 解密TS文件 :使用下载的密钥URL和IV(初始化向量),对每个加密的TS文件进行解密。这通常使用AES-128-CBC模式进行解密。例如,可以使用Python的
Crypto.Cipher
库来实现解密。
python
pip install Crypto #安装方法
- 合并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而不同,从而提供更强的安全性。
如果你正在处理一个实际的加密和解密任务,你应该确保:
- IV是随机生成的,并且每次加密时都是唯一的。
- IV与密文一起传输或存储,以便解密时可以使用相同的IV。
- IV不应该硬编码在代码中,以避免安全风险。
在处理敏感数据时,这种做法是不安全的,应该避免。正确的做法是在每次加密时生成一个新的随机IV,并将其存储在安全的地方,以便解密时可以使用。