解密M3U8播放列表,使用AES-128加密,无需IV

问题描述 投票:1回答:3

我目前正在构建一个下载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"

任何帮助是极大的赞赏。提前致谢!

c# aes hls m3u8
3个回答
2
投票

HLS规范声明[1]:

AES-128的加密方法表示媒体段使用高级加密标准(AES)[AES_128]与128位密钥,密码块链接(CBC)和公钥加密标准#7(PKCS7)完全加密填充[RFC5652]。使用初始化向量(IV)属性值或媒体序列号作为IV,在每个段边界上重新启动CBC;见5.2节。

因此,您必须在变体播放列表中使用EXT-X-MEDIA-SEQUENCE标记的值。一定要推断,即为每个段增加它。

[1] https://tools.ietf.org/html/rfc8216#section-4.3.2.4


1
投票

我通过以下方式实现了这一点(其中seqNum是此段的媒体序列号):

readonly byte[] blank8Bytes = new byte[8];

// [...]
    AES.IV = blank8Bytes.Concat(IntToBigEndianBytes(seqNum)).ToArray();
// [...]    

// https://stackoverflow.com/a/1318948/9526448
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


1
投票

我知道这已经被bzier正确回答了,但是我想我会为未来的读者提一下:

解析/解密m3u8文件可以由ffmpeg自动处理。通过查看source code,我们可以了解如何在未提供IV时建立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://tools.ietf.org/html/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();

这是ToBigEndianBytes扩展函数,我从bzier的响应中借用了它。

public static byte[] ToBigEndianBytes(this int i) {
    byte[] bytes = BitConverter.GetBytes(Convert.ToUInt64(i));
    if (BitConverter.IsLittleEndian)
        Array.Reverse(bytes);
    return bytes;
}

此代码使用PlaylistsNET来解析播放列表条目,您必须手动设置密钥/起始媒体序列 - 但它会显示加密及其工作原理。

我仍然强烈推荐使用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可以为你做。

© www.soinside.com 2019 - 2024. All rights reserved.