在没有 IV 的情况下解密使用 AES-128 加密的 M3U8 播放列表
Decrypt M3U8 Playlist encrypted with AES-128 without IV
我目前正在构建一个用于下载 M3U8 播放列表的应用程序,但我 运行 遇到了一个问题:如果播放列表使用 AES-128 加密,例如有这样一行:
#EXT-X-KEY:METHOD=AES-128,URI="https://website.com/link.key",IV=0xblablabla
我必须在将段写入输出文件之前对其进行解密,如果存在 IV,则以下代码对我有用,但如果 IV 属性 不存在,则解密会产生一个错误结果:
var iv = "parsed iv"; // empty if not present
var key_url = "parsed keyurl";
var AES = new AesManaged()
{
Mode = CipherMode.CBC,
Key = await Client.GetByteArrayAsync(key_url)
};
if (!string.IsNullOrEmpty(iv))
AES.IV = Functions.HexToByte(iv.StartsWith("0x") ? iv.Remove(0, 2) : iv);
else
AES.IV = new byte[16];
//...
using (FileStream fs = new FileStream("file.ts", FileMode.Create, FileAccess.Write, FileShare.Read))
{
var data = DownloadSegment(...); // Downloads segment as byte array (encrypted)
byte[] temp = new byte[data.Length];
ICryptoTransform transform = AES.CreateDecryptor();
using (MemoryStream memoryStream = new MemoryStream(data))
{
using (CryptoStream cryptoStream = new CryptoStream(memoryStream, transform, CryptoStreamMode.Read))
{
cryptoStream.Read(temp, 0, data.Length);
}
}
await fs.WriteAsync(temp, 0, temp.Length);
}
(这显然只是一个代码片段,包含解密部分,因为所有解析和下载都可以正常工作)。
如果不存在 IV,你们中的任何人都知道如何解密 M3U8 播放列表文件中的 AES-128 加密片段吗?刚刚
#EXT-X-KEY:METHOD=AES-128,URI="https://website.com/link.key"
?
非常感谢任何帮助。提前致谢!
HLS 规范指出 [1]:
An encryption method of AES-128 signals that Media Segments are
completely encrypted using the Advanced Encryption Standard (AES)
[AES_128] with a 128-bit key, Cipher Block Chaining (CBC), and
Public-Key Cryptography Standards #7 (PKCS7) padding [RFC5652].
CBC is restarted on each segment boundary, using either the
Initialization Vector (IV) attribute value or the Media Sequence
Number as the IV; see Section 5.2.
因此您必须在变体播放列表中使用 EXT-X-MEDIA-SEQUENCE
标签的值。一定要外推,即为每个段增加它。
我是按以下方式实现的(其中 seqNum
是该片段的媒体序列号):
readonly byte[] blank8Bytes = new byte[8];
// [...]
AES.IV = blank8Bytes.Concat(IntToBigEndianBytes(seqNum)).ToArray();
// [...]
//
private static byte[] IntToBigEndianBytes(ulong intValue)
{
byte[] intBytes = BitConverter.GetBytes(intValue);
if (BitConverter.IsLittleEndian)
Array.Reverse(intBytes);
byte[] result = intBytes;
return result;
}
仅供参考,既然你说你也在解析播放列表,我会提到我分叉了 iHeartRadio open-m3u8 播放列表解析器并将其翻译成 C#。如果您有兴趣,请访问 C# 库:https://github.com/bzier/open-m3u8
我知道 bzier 已经正确回答了这个问题,但我想我会为未来的读者提到这个:
Parsing/decrypt ffmpeg 可以自动处理 m3u8 文件。通过查看 source code,我们可以了解未提供 IV 时如何建立它。
这也记录在 RFC 8216.
如果您觉得需要自己在 C# 中执行此操作,这里有一个完整的示例:
string m3u8_url = "https://example.com/file.m3u8";
WebClient web = new WebClient();
Stream m3u8_data = web.OpenRead(m3u8_url);
web.Dispose();
M3u8Content content = new M3u8Content();
M3uPlaylist playlist = content.GetFromStream(m3u8_data);
int media_sequence = 0;
// 16 chars - steal this from the key file.
byte[] key = Encoding.ASCII.GetBytes("0123456701234567");
string path = Path.GetFullPath("output.mp4");
FileStream fs = File.Create(path);
foreach(M3uPlaylistEntry entry in playlist.PlaylistEntries) {
// establish initialization vector
// note: iv must be 16 bytes (AES-128)
byte[] iv = media_sequence.ToBigEndianBytes(); // 8 bytes
iv = new byte[8].Concat(iv).ToArray(); // add 8 empty bytes to beginning
// https://www.rfc-editor.org/rfc/rfc8216#section-4.3.2.4
// HLS uses AES-128 w/ CBC & PKCS7
RijndaelManaged algorithm = new RijndaelManaged() {
Padding = PaddingMode.PKCS7,
Mode = CipherMode.CBC,
KeySize = 128,
BlockSize = 128
};
// key = from uri in m3u8 file
// iv = derived from sequence number
algorithm.Key = key;
algorithm.IV = iv;
web = new WebClient();
byte[] data = web.DownloadData(entry.Path);
// create memorystream to store bytes & cryptostream to decrypt
MemoryStream ms = new MemoryStream();
CryptoStream cs = new CryptoStream(ms, algorithm.CreateDecryptor(), CryptoStreamMode.Write);
// decrypt data to memorystream
cs.Write(data, 0, data.Length);
// write decrypted bytes to our mp4 file
byte[] bytes = ms.ToArray();
fs.Write(bytes, 0, bytes.Length);
// close/dispose those streams
cs.Close();
ms.Close();
cs.Dispose();
ms.Dispose();
// increment media sequence to update initialization vector
media_sequence++;
}
// close the file stream & dispose of it
fs.Close();
fs.Dispose();
这是我从 bzier 的回复中借用的 ToBigEndianBytes 扩展函数。
public static byte[] ToBigEndianBytes(this int i) {
byte[] bytes = BitConverter.GetBytes(Convert.ToUInt64(i));
if (BitConverter.IsLittleEndian)
Array.Reverse(bytes);
return bytes;
}
此代码使用 PlaylistsNET 解析播放列表条目,您必须手动设置 key/starting 媒体序列 - 但它展示了加密及其工作原理。
不过我仍然强烈推荐使用 ffmpeg。
string cmd = string.Format("ffmpeg -i \"{0}\" -c copy -bsf:a aac_adtstoasc \"{1}\"", m3u8_url, local_mp4_path);
Execute(cmd);
public static void ExecuteCommand(string command) {
Process process = new Process();
ProcessStartInfo startInfo = new ProcessStartInfo();
startInfo.WindowStyle = ProcessWindowStyle.Hidden;
startInfo.FileName = "cmd.exe";
startInfo.Arguments = "/C " + command;
process.StartInfo = startInfo;
process.Start();
process.WaitForExit();
}
它将用更少的代码完成同样的事情,而且您不必将生成的 .ts 文件转换为 .mp4,因为 ffmpeg 可以为您完成。
我目前正在构建一个用于下载 M3U8 播放列表的应用程序,但我 运行 遇到了一个问题:如果播放列表使用 AES-128 加密,例如有这样一行:
#EXT-X-KEY:METHOD=AES-128,URI="https://website.com/link.key",IV=0xblablabla
我必须在将段写入输出文件之前对其进行解密,如果存在 IV,则以下代码对我有用,但如果 IV 属性 不存在,则解密会产生一个错误结果:
var iv = "parsed iv"; // empty if not present
var key_url = "parsed keyurl";
var AES = new AesManaged()
{
Mode = CipherMode.CBC,
Key = await Client.GetByteArrayAsync(key_url)
};
if (!string.IsNullOrEmpty(iv))
AES.IV = Functions.HexToByte(iv.StartsWith("0x") ? iv.Remove(0, 2) : iv);
else
AES.IV = new byte[16];
//...
using (FileStream fs = new FileStream("file.ts", FileMode.Create, FileAccess.Write, FileShare.Read))
{
var data = DownloadSegment(...); // Downloads segment as byte array (encrypted)
byte[] temp = new byte[data.Length];
ICryptoTransform transform = AES.CreateDecryptor();
using (MemoryStream memoryStream = new MemoryStream(data))
{
using (CryptoStream cryptoStream = new CryptoStream(memoryStream, transform, CryptoStreamMode.Read))
{
cryptoStream.Read(temp, 0, data.Length);
}
}
await fs.WriteAsync(temp, 0, temp.Length);
}
(这显然只是一个代码片段,包含解密部分,因为所有解析和下载都可以正常工作)。
如果不存在 IV,你们中的任何人都知道如何解密 M3U8 播放列表文件中的 AES-128 加密片段吗?刚刚
#EXT-X-KEY:METHOD=AES-128,URI="https://website.com/link.key"
?
非常感谢任何帮助。提前致谢!
HLS 规范指出 [1]:
An encryption method of AES-128 signals that Media Segments are completely encrypted using the Advanced Encryption Standard (AES) [AES_128] with a 128-bit key, Cipher Block Chaining (CBC), and Public-Key Cryptography Standards #7 (PKCS7) padding [RFC5652]. CBC is restarted on each segment boundary, using either the Initialization Vector (IV) attribute value or the Media Sequence Number as the IV; see Section 5.2.
因此您必须在变体播放列表中使用 EXT-X-MEDIA-SEQUENCE
标签的值。一定要外推,即为每个段增加它。
我是按以下方式实现的(其中 seqNum
是该片段的媒体序列号):
readonly byte[] blank8Bytes = new byte[8];
// [...]
AES.IV = blank8Bytes.Concat(IntToBigEndianBytes(seqNum)).ToArray();
// [...]
//
private static byte[] IntToBigEndianBytes(ulong intValue)
{
byte[] intBytes = BitConverter.GetBytes(intValue);
if (BitConverter.IsLittleEndian)
Array.Reverse(intBytes);
byte[] result = intBytes;
return result;
}
仅供参考,既然你说你也在解析播放列表,我会提到我分叉了 iHeartRadio open-m3u8 播放列表解析器并将其翻译成 C#。如果您有兴趣,请访问 C# 库:https://github.com/bzier/open-m3u8
我知道 bzier 已经正确回答了这个问题,但我想我会为未来的读者提到这个:
Parsing/decrypt ffmpeg 可以自动处理 m3u8 文件。通过查看 source code,我们可以了解未提供 IV 时如何建立它。
这也记录在 RFC 8216.
如果您觉得需要自己在 C# 中执行此操作,这里有一个完整的示例:
string m3u8_url = "https://example.com/file.m3u8";
WebClient web = new WebClient();
Stream m3u8_data = web.OpenRead(m3u8_url);
web.Dispose();
M3u8Content content = new M3u8Content();
M3uPlaylist playlist = content.GetFromStream(m3u8_data);
int media_sequence = 0;
// 16 chars - steal this from the key file.
byte[] key = Encoding.ASCII.GetBytes("0123456701234567");
string path = Path.GetFullPath("output.mp4");
FileStream fs = File.Create(path);
foreach(M3uPlaylistEntry entry in playlist.PlaylistEntries) {
// establish initialization vector
// note: iv must be 16 bytes (AES-128)
byte[] iv = media_sequence.ToBigEndianBytes(); // 8 bytes
iv = new byte[8].Concat(iv).ToArray(); // add 8 empty bytes to beginning
// https://www.rfc-editor.org/rfc/rfc8216#section-4.3.2.4
// HLS uses AES-128 w/ CBC & PKCS7
RijndaelManaged algorithm = new RijndaelManaged() {
Padding = PaddingMode.PKCS7,
Mode = CipherMode.CBC,
KeySize = 128,
BlockSize = 128
};
// key = from uri in m3u8 file
// iv = derived from sequence number
algorithm.Key = key;
algorithm.IV = iv;
web = new WebClient();
byte[] data = web.DownloadData(entry.Path);
// create memorystream to store bytes & cryptostream to decrypt
MemoryStream ms = new MemoryStream();
CryptoStream cs = new CryptoStream(ms, algorithm.CreateDecryptor(), CryptoStreamMode.Write);
// decrypt data to memorystream
cs.Write(data, 0, data.Length);
// write decrypted bytes to our mp4 file
byte[] bytes = ms.ToArray();
fs.Write(bytes, 0, bytes.Length);
// close/dispose those streams
cs.Close();
ms.Close();
cs.Dispose();
ms.Dispose();
// increment media sequence to update initialization vector
media_sequence++;
}
// close the file stream & dispose of it
fs.Close();
fs.Dispose();
这是我从 bzier 的回复中借用的 ToBigEndianBytes 扩展函数。
public static byte[] ToBigEndianBytes(this int i) {
byte[] bytes = BitConverter.GetBytes(Convert.ToUInt64(i));
if (BitConverter.IsLittleEndian)
Array.Reverse(bytes);
return bytes;
}
此代码使用 PlaylistsNET 解析播放列表条目,您必须手动设置 key/starting 媒体序列 - 但它展示了加密及其工作原理。
不过我仍然强烈推荐使用 ffmpeg。
string cmd = string.Format("ffmpeg -i \"{0}\" -c copy -bsf:a aac_adtstoasc \"{1}\"", m3u8_url, local_mp4_path);
Execute(cmd);
public static void ExecuteCommand(string command) {
Process process = new Process();
ProcessStartInfo startInfo = new ProcessStartInfo();
startInfo.WindowStyle = ProcessWindowStyle.Hidden;
startInfo.FileName = "cmd.exe";
startInfo.Arguments = "/C " + command;
process.StartInfo = startInfo;
process.Start();
process.WaitForExit();
}
它将用更少的代码完成同样的事情,而且您不必将生成的 .ts 文件转换为 .mp4,因为 ffmpeg 可以为您完成。