针对m3u8视频的ts文件解密

您所在的位置:网站首页 优酷怎么破解加密 针对m3u8视频的ts文件解密

针对m3u8视频的ts文件解密

2023-10-06 02:15| 来源: 网络整理| 查看: 265

[C#/Java] 针对 QINIU-PROTECTION-10 的m3u8视频文件解密

源码地址:https://github.com/Myron1024/m3u8_download

今年上网课很流行,有些在线的课程视频想下载下来到本地看,发现视频的链接是m3u8格式的,下载下来后,提取出视频切片的各个.ts文件的链接,把这些视频片段下载到本地后,却播放不了。于是就花点时间研究研究。网上了解了一下情况,知道视频是加密的, 不过搜了一大圈,都是讲的加密方式为 METHOD=AES-128 的解密方法,可我下载的m3u8文件打开看是 METHOD=QINIU-PROTECTION-10

 

了解到解密视频需要key和IV, 我们可以看到 IV在m3u8文件里有,每一个.ts文件都有一个对应的IV,#EXT-X-KEY:后面的 IV=**** 就是我们需要用到的 IV了, 可是key却没有,那就只能从网页上找找了,打开控制台,重新加载页面,发现一个 qiniu-web-player.js 在控制台输出了一些配置信息和日志记录,其中 hls.DRMKey 引起了我的注意

 

  

数组长度也是16位,刚好加解密用到的key的长度也是16位,, 所以这个应该就是AES加解密要用到的key了,不过需要先转换一下。。

网上的方法 转换步骤为:把数组里每一位数字转换成16进制字符串,然后把16进制字符串转为ASCII码,最终拼接出来的结果就是AES的key了。

C#代码:

复制代码 private static string getAESKey(string key) { string[] arr = key.Split(","); string aesKey = ""; for (int i = 0; i < arr.Length; i++) { string tmp = int.Parse(arr[i].Trim()).ToString("X"); //10进制转16进制 tmp = HexStringToASCII(tmp); aesKey += tmp; } return aesKey; } /// /// 十六进制字符串转换为ASCII /// /// 一条十六进制字符串 /// 返回一条ASCII码 public static string HexStringToASCII(string hexstring) { byte[] bt = HexStringToBinary(hexstring); string lin = ""; for (int i = 0; i < bt.Length; i++) { lin = lin + bt[i] + " "; } string[] ss = lin.Trim().Split(new char[] { ' ' }); char[] c = new char[ss.Length]; int a; for (int i = 0; i < c.Length; i++) { a = Convert.ToInt32(ss[i]); c[i] = Convert.ToChar(a); } string b = new string(c); return b; } 复制代码

把js获取的DRMKey数组内容当做字符串传入,获取AES的key

string DRMKey = "11, 22, 33, 44, 55, 66, 77, 88, 99, 00, 111, 111, 111, 111, 111, 111"; string aesKey = getAESKey(DRMKey); Console.WriteLine("aesKey:" + aesKey);

 

现在AES_KEY和IV都有了,可以加解密了,不过这个IV有点特殊,是32位的,我们需要进行切片取前16位,16位是固定位数,必须这么取。

通过分析页面js代码得知这种AES的加密模式为CBC模式,PaddingMode采用PKCS7.

加密模式、补码方式、key、IV都有了,剩下的就是编码测试了。

 

下面是C#版的完整代码, Java版请看这里

复制代码 using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Net; using System.Security.Cryptography; using System.Text; using System.Text.RegularExpressions; namespace VideoDownload { class Program { private static List error_arr = new List(); static void Main(string[] args) { string DRMKey = "11, 22, 33, 44, 55, 66, 77, 88, 99, 00, 111, 111, 111, 111, 111, 111"; //DRMKey string m3u8Url = "https://XXXXXXX/123.m3u8"; //m3u8在线地址 string savePath = "D:\\VIDEO\\"; //保存的本地路径 string saveFileName = "VIDEO_FILE_NAME"; //保存的文件(夹)名称,如果为空 则使用默认m3u8文件名 try { // 创建本地保存目录 int index = m3u8Url.LastIndexOf("/"); string dirName = string.IsNullOrEmpty(saveFileName) ? m3u8Url.Substring(index + 1) : saveFileName; string finalSavePath = savePath + dirName + "\\"; if (!Directory.Exists(finalSavePath)) { Directory.CreateDirectory(finalSavePath); } // 读取m3u8文件内容 string m3u8Content = HttpGet(m3u8Url); //string m3u8Content = File.ReadAllText("D:/test.m3u8"); string aesKey = getAESKey(DRMKey); //Console.WriteLine("aesKey:" + aesKey); Uri uri = new Uri(m3u8Url); string domain = uri.Scheme + "://" + uri.Authority; //Console.WriteLine("m3u8域名为:" + domain); List tsList = Regex.Matches(m3u8Content, @"\n(.*?.ts)").Select(m => m.Value).ToList(); List ivList = Regex.Matches(m3u8Content, @"IV=(.*?)\n").Select(m => m.Value).ToList(); if (tsList.Count != ivList.Count || tsList.Count == 0) { Console.WriteLine("m3u8Content 解析失败"); } else { Console.WriteLine("m3u8Content 解析完成,共有 " + ivList.Count + " 个ts文件"); for (int i = 0; i < tsList.Count; i++) { string ts = tsList[i].Replace("\n", ""); string iv = ivList[i].Replace("\n", ""); iv = iv.Replace("IV=0x", ""); iv = iv.Substring(0, 16); //去除前缀,取IV前16位 int idx = ts.LastIndexOf("/"); string tsFileName = ts.Substring(idx + 1); try { string saveFilepath = finalSavePath + tsFileName; if (!File.Exists(saveFilepath)) { Console.WriteLine("开始下载ts: " + domain + ts); byte[] encByte = HttpGetByte(domain + ts); if (encByte != null) { Console.WriteLine("开始解密, IV -> " + iv); byte[] decByte = null; try { decByte = AESDecrypt(encByte, aesKey, iv); } catch (Exception e1) { error_arr.Add(tsFileName); Console.WriteLine("解密ts文件异常。" + e1.Message); } if (decByte != null) { //保存视频文件 File.WriteAllBytes(saveFilepath, decByte); Console.WriteLine(tsFileName + " 下载完成"); } } else { error_arr.Add(tsFileName); Console.WriteLine("HttpGetByte 结果返回null"); } } else { Console.WriteLine($"文件 {saveFilepath} 已存在"); } } catch (Exception ee) { error_arr.Add(tsFileName); Console.WriteLine("发生异常。" + ee); } } } } catch (Exception ex) { Console.WriteLine("发生异常。" + ex); } Console.WriteLine("所有操作已完成. 保存目录 " + savePath); if (error_arr.Count > 0) { List list = error_arr.Distinct().ToList(); Console.WriteLine($"其中 共有{error_arr.Count}个文件下载失败:"); list.ForEach(x => { Console.WriteLine(x); }); } Console.ReadKey(); } private static string getAESKey(string key) { string[] arr = key.Split(","); string aesKey = ""; for (int i = 0; i < arr.Length; i++) { string tmp = int.Parse(arr[i].Trim()).ToString("X"); //10进制转16进制 tmp = HexStringToASCII(tmp); aesKey += tmp; } return aesKey; } /// /// 十六进制字符串转换为ASCII /// /// 一条十六进制字符串 /// 返回一条ASCII码 public static string HexStringToASCII(string hexstring) { byte[] bt = HexStringToBinary(hexstring); string lin = ""; for (int i = 0; i < bt.Length; i++) { lin = lin + bt[i] + " "; } string[] ss = lin.Trim().Split(new char[] { ' ' }); char[] c = new char[ss.Length]; int a; for (int i = 0; i < c.Length; i++) { a = Convert.ToInt32(ss[i]); c[i] = Convert.ToChar(a); } string b = new string(c); return b; } /// /// 16进制字符串转换为二进制数组 /// /// 用空格切割字符串 /// 返回一个二进制字符串 public static byte[] HexStringToBinary(string hexstring) { string[] tmpary = hexstring.Trim().Split(' '); byte[] buff = new byte[tmpary.Length]; for (int i = 0; i < buff.Length; i++) { buff[i] = Convert.ToByte(tmpary[i], 16); } return buff; } /// /// AES解密 /// /// /// /// /// public static byte[] AESDecrypt(byte[] cipherText, string Key, string IV) { // Check arguments. if (cipherText == null || cipherText.Length 0) { stmMemory.Write(buffer1, 0, i); } arraryByte = stmMemory.ToArray(); stmMemory.Close(); } } return arraryByte; } catch (Exception ex) { Console.Write("HttpGetByte 异常," + ex.Message); Console.Write(ex); return null; } } } } 复制代码

新建个控制台应用,代码复制过去,改一下最上面的四个参数值就可以运行。本来想做个桌面应用程序的,结果嫌麻烦,费时间就没做了。哪位看官要是有时间可以做个桌面程序方便操作,另外可以加上多线程去下载会快一些。下载解密完之后的ts文件后,使用其他工具合并ts文件或者用windows自带cmd执行以下命令也可以合并文件

在命令行中输入:copy /b D:\VIDEO\*.ts D:\VIDEO\newFile.ts

 

 

出处:https://www.cnblogs.com/myron1024/p/13532379.html

=======================================================================================

我这几天也下载了网上的视频,其中m3u8和ts文件格式如下:

 

 其中也是有了AES-128加密的,其中key.key文件的内容就是:c355b7c32d7b97ed

有了pk,还需要iv,分析文件名应该像,但又不符合长度,每个文件名重复一遍就是16位了,试试看,还真蒙对了

这里要说明下:从网上找了很多AES解密的算法都不对,可能是缺少了某些aes加密时的配置。

我这里也根据上面的代码,根据自己的需求修改了一下:

using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using System.IO; namespace AES_128 { class Program { static void Main(string[] args) { string pk = ""; string iv = ""; string strFolder = @"D:\_Del\00"; if (args.Length != 3) { Console.WriteLine($"参数异常:未指定待解密文件的目录,以及public key和iv的值"); Console.WriteLine($"调用方式:"); Console.WriteLine($"{AppDomain.CurrentDomain.SetupInformation.ApplicationName} 待解密文件夹名 -pk=值 -iv=值"); Console.WriteLine($"\r\n\r\n按任意键继续......"); Console.ReadKey(); return; } strFolder = args[0]; pk = args[1].Split('=')[1]; iv = args[2].Split('=')[1]; DirectoryInfo di = new DirectoryInfo(strFolder); string tagFolder = strFolder + @"\Dec\"; DelFolder(tagFolder); var fs = di.GetFiles(); int secessCount = 0; List errList = new List(); foreach (var item in fs) { var fc = ReadFileContent(item.FullName); iv = item.Name.Substring(5, 8); Console.WriteLine($"开始处理:{item.Name},size={fc.Length}"); try { var fc_de = Comm.AES_EnorDecrypt.AESDecrypt2(fc, pk, iv + iv); string tagFile = tagFolder + item.Name; WriteFileContent(tagFile, fc_de); Console.WriteLine($"解密处理已完成,已保存在:{tagFile},size={fc_de.Length}"); secessCount++; } catch (Exception) { errList.Add(item); } } Console.WriteLine($"\r\n解密处理{fs.Count()}个文件,成功解密完成{secessCount}个文件"); if (errList.Count() > 0) { Console.WriteLine($"\r\n解密失败的文件如下:"); foreach (var item in errList) Console.WriteLine(item.Name); } Console.ReadKey(); } /// /// AES解密 /// /// /// /// /// public static byte[] AESDecrypt2(byte[] cipherText, string Key, string IV) { // Check arguments. if (cipherText == null || cipherText.Length


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3