YouTube视频签名加密算法的破解
密码学方法
多年以前,YouTube的视频源地址是直接encode在页面中的,你甚至可以用一行Perl来下载它们。
直到2012年8月,这个简单的脚本(用在0.0.1版本的You-Get中)仍然可以解析出YouTube视频的源地址。(利用页面的url_encoded_fmt_stream_map
中提供的信息)
大约在9月的时候,YouTube采取了反下载措施。这个所谓的措施就是在视频源地址里加上了一个signature
参数,需要单独解析出url_encoded_fmt_stream_map
中的url
域和sig
域后合并(将sig
的值直接用&signature=
连接到原url
的后面即可)。未加signature
参数或signature
不正确的地址会返回403 Forbidden错误。
最近几天,YouTube开始部署更进一步的反下载措施了。很多视频(似乎主要是Copyrighted material)的url_encoded_fmt_stream_map
中不再有sig
域(即原来可直接使用的signature
参数),取而代之的是一个看起来较相似的s
域(实际上是经过加密的signature
),同时多了一个域use_cipher_signature=True
。(参见youtube-dl的讨论)
在浏览器中播放某YouTube视频(http://youtu.be/FU8csnZxdPA),抓取到的实际地址是:
http://r13---sn-5uaeznl7.c.youtube.com/videoplayback?algo
rithm=throttle-factor&burst=40&clen=11611894&cp=U0hWR1RPU
LUENONl9MSVdDOmJqRU8tZ1VSNFll&cpn=dLOl8I7n-YcCtPDe&expi
re=1372445266&factor=1.25&fexp=931311%2C929816%2C923002%2
C907227%2C930504%2C906397%2C928201%2C929123%2C929915%2C92
%2C929907%2C929125%2C929127%2C925714%2C929917%2C92991
%2C931202%2C912512%2C912515%2C912521%2C906838%2C906840%2
C931913%2C904830%2C919373%2C933701%2C904122%2C932211%2C93
%2C900816%2C909421%2C912711%2C907228&gir=yes&id=154f1
cb2767174f0&ip=8.35.201.112&ipbits=8&itag=134&keepalive=y
es&key=yt1&lmt=1369097054006774&ms=au&mt=1372422395&mv=m&
newshard=yes&range=1802240-2703359&ratebypass=yes&signatu
re=4A5F3E1E4317AB31BD81E1A155E39C01F8C8BD48.6EBE5DDC5720C
C6B1927402C84DCBF0E52&source=youtube&sparams=algori
thm%2Cburst%2Cclen%2Ccp%2Cfactor%2Cgir%2Cid%2Cip%2Cipbits
%2Citag%2Clmt%2Csource%2Cupn%2Cexpire&sver=3&upn=yYnqjUrC
Ty8
抽出URL中的signature
参数,为:
A5F3E1E4317AB31BD81E1A155E39C01F8C8BD48.
EBE5DDC5720C003487C6B1927402C84DCBF0E52
可以看到,该字符串是用"."
连接的两个40位16进制数(总长度40+1+40=81)。由于该视频地址的itag=134
,在视频页面的url_encoded_fmt_stream_map
adaptive_fmts
中找到itag=134
所对应的s
,为:
A4A5F3E1E4317AB31BD81E1A155E39C01F8C8BD48.
EBE5DDC5720C003487C2B1927402C84DCBF0E56E56
这是一个被加密的signature
,长度为42+1+43=86,无法直接用于访问视频源地址。注意:每次HTTP request时YouTube均会根据客户端IP自动生成新的s
(和signature
),故抓取视频源URL和页面中与之匹配的s
必须在同一次request中完成。
所以,现在我们要做的,就是找到从这个长度86位的字符串s
(可直接从视频页面中获得)转换到原来的标准81位字符串sig
(真正的YouTube视频签名,可通过抓取浏览器request的真实地址获得)的算法。
逆向工程破解加密算法本来是一件颇有难度的任务,不过,肉眼观察一下加密前sig
和加密后s
的模式就很容易发现,其中相同的公共子串部分相当可观。大致可以合理猜测,这里的加密只是最基本的字符串重新排列组合而已(即单一的permutation cipher,可看做是移位式密码(transposition cipher)的一种)。
因为被加密的字符串本身并不是自然语言,这造成了一定的难度,例如无法通过易位构词法(anagramming)来尝试破解。此外,理论上,将一个长度86的字符串通过移位加密成一个长度81的字符串,可能存在的方式有
$P^{86}_{81} = \frac{86!}{(86-81)!} = 2.0189 \times 10^{128}$
种。这是一个天文数字。穷举法当然也是行不通的。
然而,在给出了第一对已知的s
和sig
字符串之后,搜索空间可以被大大地缩减。若能给出更多的s
和sig
字符串对,我们将能够很快地确定唯一可行的移位加密方式。
尝试分析这个81位的sig
字符串:
A5F3E1E4317AB31BD81E1A155E39C01F8C8BD48.
EBE5DDC5720C003487C6B1927402C84DCBF0E52
对比加密后的86位s
字符串,可得到sig
中每一位可能对应的移位方式:
: sig[0] 可能来自于 => s[0, 2, 10, 40, 59, 69, 74]
A: sig[1] 可能来自于 => s[1, 3, 14, 24]
: sig[2] 可能来自于 => s[4, 26, 27, 47, 51, 81, 84]
F: sig[3] 可能来自于 => s[5, 34, 78]
: sig[4] 可能来自于 => s[6, 11, 16, 29, 58]
E: sig[5] 可能来自于 => s[7, 9, 22, 28, 44, 46, 80, 83]
...
作为单一的移位式密码,s
中的每一个字符只可能在sig
中被使用一次,有了这个限制条件,通过搜索可以剪掉明显不可能的解,从而得到较小的解空间。但是对于81位的字符串sig
来说,这个搜索的时间代价仍然是难以承受的。
所幸,在这里为了破解加密算法,其实完全没有搜索的必要,因为我们可以任意收集到无限多的s
和sig
字符串对,通过它们直接排除掉不合理的对应关系,最终得到唯一的加密方式。(这就叫做算法竞赛和解决现实问题的差异……)
写了一个简单的Ruby程序用于求解:
begin
puts "s:"
s = gets.chomp
puts "sig:"
sig = gets.chomp
dic = {}
s.chars.each_index do |i|
dic[s[i]] = [] if dic[s[i]].nil?
dic[s[i]] << i
end
sol = [(0...s.length).to_a] * sig.length if sol.nil?
sig.chars.each_index do |i|
sol[i] &= dic[sig[i]]
end
p sol
end while sol.flatten.length > sig.length
每次读入一对s
和sig
,将s
中的不同字符分布位置存入一个Hash表,然后根据sig
每一位字符所可能对应的在s
中的分布位置,对sol
数组中的可能解加以过滤,直至得出唯一的解(sol.flatten.length == sig.length
)。
输入第一对s
和sig
,得到一个较大的初始解空间:
s:
A4A5F3E1E4317AB31BD81E1A155E39C01F8C8BD48.
EBE5DDC5720C003487C2B1927402C84DCBF0E56E56
sig:
A5F3E1E4317AB31BD81E1A155E39C01F8C8BD48.
EBE5DDC5720C003487C6B1927402C84DCBF0E52
[[0, 2, 10, 40, 59, 69, 74], [1, 3, 14, 24], [4, 26,
, 47, 51, 81, 84], [5, 34, 78], [6, 11, 16, 29, 58],
[7, 9, 22, 28, 44, 46, 80, 83], [8, 12, 17, 21, 23, 25,
, 65], [7, 9, 22, 28, 44, 46, 80, 83], [0, 2, 10, 40,
, 69, 74], [6, 11, 16, 29, 58], [8, 12, 17, 21, 23,
, 33, 65], [13, 52, 61, 68], [1, 3, 14, 24], [15, 18,
, 45, 64, 77], [6, 11, 16, 29, 58], [8, 12, 17, 21,
, 25, 33, 65], [15, 18, 38, 45, 64, 77], [19, 39, 48,
, 75], [20, 35, 37, 41, 60, 73], [8, 12, 17, 21, 23,
, 33, 65], [7, 9, 22, 28, 44, 46, 80, 83], [8, 12,
, 21, 23, 25, 33, 65], [1, 3, 14, 24], [8, 12, 17,
, 23, 25, 33, 65], [4, 26, 27, 47, 51, 81, 84], [4,
, 27, 47, 51,81,84],[7,9,22,28,44,46,80,],[6,11,16,29,58],[30,66],[31,36,50,55,,72,76],[32,54,56,57,70,79],[8,12,17,21,,25,33,65],[5,34,78],[20,35,37,41,60,73],[31,36,50,55,62,72,76],[20,35,37,41,60,73],[15,18,38,45,64,77],[19,39,48,49,75],[0,2,,40,59,69,74],[20,35,37,41,60,73],[42],[43,82,85],[7,9,22,28,44,46,80,83],[15,18,,45,64,77],[7,9,22,28,44,46,80,83],[4,,27,47,51,81,84],[19,39,48,49,75],[19,39,,49,75],[31,36,50,55,62,72,76],[4,26,27,,51,81,84],[13,52,61,68],[53,63,67,71],[32,54,56,57,70,79],[31,36,50,55,62,72,76],[32,54,56,57,70,79],[32,54,56,57,70,79],[6,,16,29,58],[0,2,10,40,59,69,74],[20,35,,41,60,73],[13,52,61,68],[31,36,50,55,62,,76],[43,82,85],[15,18,38,45,64,77],[8,,17,21,23,25,33,65],[30,66],[53,63,67,],[13,52,61,68],[0,2,10,40,59,69,74],[32,,56,57,70,79],[53,63,67,71],[31,36,50,55,,72,76],[20,35,37,41,60,73],[0,2,10,40,,69,74],[19,39,48,49,75],[31,36,50,55,62,,76],[15,18,38,45,64,77],[5,34,78],[32,,56,57,70,79],[7,9,22,28,44,46,80,83],[4,26,27,47,51,81,84],[53,63,67,71]]
用Chrome DevTools抓取另一对s
和sig
,可能解的范围大大缩小了:
s:
E5ADB020E576DB28E54EE11690138B15207F6.
D02D483BF44090A079B60177D17F908ED455D6FD6F
sig:
E5ADB020E576DB28E54EE11690138B15207F6.
D02D483BF44090A079BF0177D17F908ED455D66
[[0, 2], [1, 3], [4, 26, 27], [5], [6], [7], [8, 17],
[9], [10, 59], [11], [12], [13], [14], [15], [16], [8,
], [18], [19], [20], [21, 25], [22], [23], [24], [21,
], [4, 26, 27], [4, 26, 27], [28], [29], [30], [31],
[32], [33], [34], [35], [36], [37], [38, 45, 64], [39],
[40], [41], [42], [43], [44, 80, 83], [38, 45, 64],
[46], [47], [48], [49], [50], [51], [52], [53], [54],
[55], [56], [57], [58], [10, 59], [60], [61], [62],
[82, 85], [38, 45, 64], [65], [66], [67], [68], [69],
[70], [71], [72], [73], [74], [75], [76], [77], [78],
[79], [44, 80, 83], [81, 84], [63]]
继续另一对s
和sig
:
s:
D3D3B8E64E0C31099E73AB2DB61F4CDF55E9B37FF3.
AE0A4DC3AAAE8A8DCD6253ACA9B3EC3F09D854EB4EB
sig:
D3B8E64E0C31099E73AB2DB61F4CDF55E9B37FF3.
AE0A4DC3AAAE8A8DCD62B3ACA9B3EC3F09D854E5
[[0, 2], [1, 3], [4], [5], [6], [7], [8], [9], [10],
[11], [12], [13], [14], [15], [16], [17], [18], [19],
[20], [21], [22], [23], [24], [25], [26], [27], [28],
[29], [30], [31], [32], [33], [34], [35], [36], [37],
[38], [39], [40], [41], [42], [43], [44], [45], [46],
[47], [48], [49], [50], [51], [52], [53], [54], [55],
[56], [57], [58], [59], [60], [61], [62], [82, 85],
[64], [65], [66], [67], [68], [69], [70], [71], [72],
[73], [74], [75], [76], [77], [78], [79], [80, 83],
[81, 84], [63]]
继续:
s:
A038284DCE4E964EB8A51519844254E7C08180.
E05FA11A60B2520E199005C8D07AAC433433
sig:
A038284DCE4E964EB8A51519844254E7C08180.
E05FA11A60B2530E199005C8D07AAC432
[[0, 2], [1, 3], [4], [5], [6], [7], [8], [9], [10],
[11], [12], [13], [14], [15], [16], [17], [18], [19],
[20], [21], [22], [23], [24], [25], [26], [27], [28],
[29], [30], [31], [32], [33], [34], [35], [36], [37],
[38], [39], [40], [41], [42], [43], [44], [45], [46],
[47], [48], [49], [50], [51], [52], [53], [54], [55],
[56], [57], [58], [59], [60], [61], [62], [82, 85],
[64], [65], [66], [67], [68], [69], [70], [71], [72],
[73], [74], [75], [76], [77], [78], [79], [80, 83],
[81, 84], [63]]
结果与上一次相比没有变化,似乎并没有收敛到唯一解。观察发现,每次抓取的字符串s
的s[0..1]
位与s[2..3]
位总是相同,s[80..82]
位与s[83..85]
位总是相同,由此可以合理推测,这个86位字符串s
的首2位和末3位是冗余的填充位。
故最终得到的唯一解应当是:
sol = [[2], [3], [4], [5], [6], [7], [8], [9], [10],
[11], [12], [13], [14], [15], [16], [17], [18], [19],
[20], [21], [22], [23], [24], [25], [26], [27], [28],
[29], [30], [31], [32], [33], [34], [35], [36], [37],
[38], [39], [40], [41], [42], [43], [44], [45], [46],
[47], [48], [49], [50], [51], [52], [53], [54], [55],
[56], [57], [58], [59], [60], [61], [62], [82], [64],
[65], [66], [67], [68], [69], [70], [71], [72], [73],
[74], [75], [76], [77], [78], [79], [80], [81], [63]]
为了验证其正确性,我们抓取另一个YouTube视频的s
和sig
对:
s:
C5C046A9B0A7FB13CFAC082C04500D0D47A37A7C8.
D41117823F5351AA983D8F83B61DE705368368
sig:
C046A9B0A7FB13CFAC082C04500D0D47A37A7C8.
D41117823F5351A8983D8F83B61DE70536A
对s
执行sol
数组中的移位算法所得到的结果:
sol.flatten!
=> [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29,
, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43,
, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57,
, 59, 60, 61, 62, 82, 64, 65, 66, 67, 68, 69, 70, 71,
, 73, 74, 75, 76, 77, 78, 79, 80, 81, 63]
sig = (0...81).map { |i| s[sol[i]] }.join
=> "5C046A9B0A7FB13CFAC082C04500D0D47A37A7C8.
D41117823F5351A8983D8F83B61DE70536A"
与抓到的正确sig
相吻合。
至此,我们已经得到了从86位s
字符串解密出真实signature
的具体方法。把这个移位算法改写成简明的Python实现,就是:
def decrypt86(s):
if len(s) == 86:
return s[2:63] + s[82] + s[64:82] + s[63]
...
这就是youtube-dl中用到的签名解密算法(You-Get直接把它抄过来了)。在前面,我们已经破解了len(s) == 86
的情况,针对其他长度s
的破解自然也就不是什么难事。(可能会出现的加密签名s
长度在82~88之间,每一个长度均对应一个特定的加密算法)
(请看jwz大神对YouTube此小儿科举动的吐槽……)
非密码学方法
事情还没有结束。
可以看到,YouTube目前对视频签名的加密方式不过是单一的transposition cipher而已,同时明文(sig
字符串)和密文(s
字符串)的样本可以被用户无限制地任意获得(通过Chrome DevTools或类似的工具抓取),这导致了它对于前面这种已知明文攻击(KPA,Known-plaintext attack)非常脆弱。但是,如果未来YouTube再次改进它的加密方式,采取复合式的密码算法,甚至哪怕是简单的substitution + transposition cipher结合,都可能会使破解的困难程度大大增加。
这种时候,我们其实还有更好的方法。
在浏览器中访问视频页面http://www.youtube.com/watch?v=FU8csnZxdPA,在源码中找到类似这样的JSON数据:
"assets": {"js": "http:\/\/s.ytimg.com\/yts\/jsbin\/html5player-vfl_ymO4Z.js",
在http://s.ytimg.com/yts/jsbin/html5player-vfl_ymO4Z.js这个文件中定位相关部分的代码:(阅读这种东西是最考验耐心的时候)
function ur(a,b,c){for(var d=[],e=0;e<a[K];e++){var
g=a[e];if(g.sig||g.s){var h=g.sig||Qo(g.s);g.url=
Fo(g.url,{signature:h})}g.url&&d[G](tr(g.url,g[J],
g.quality,g.itag,g.stereo3d))}return or(d,!!b,!!c)}
可借助JavaScript beautifier之类的工具将其格式之:
function ur(a, b, c) {
for (var d = [],e = 0; e < a[K]; e++) {
var g = a[e];
if (g.sig || g.s) {
var h = g.sig || Qo(g.s);
g.url = Fo(g.url, {signature: h})
}
g.url && d[G](tr(g.url, g[J], g.quality, g.itag, g.stereo3d))
}
return or(d, !!b, !!c)
}
看到var h = g.sig || Qo(g.s)
这一句,就能大致猜出Qo()
是用来对s
解密计算出sig
的函数了。同样,找到Qo()
的定义:
function Qo(a){a=a[y]("");a=a.reverse();a=a[he](3);
var b=a[0];a[0]=a[19%a[K]];a[19]=b;a=a.reverse();
a=a[he](2);return a[O]("")};
以及该函数中用到的若干外部定义:
y="split",
he="slice",
K="length",
O="join",
整理一下,把它们放到一个独立的ECMAScript脚本中:
#!/usr/bin/env js
var
y = "split",
he = "slice",
K = "length",
O = "join";
function Qo(a) {
a = a[y]("");
a = a.reverse();
a = a[he](3);
var b = a[0];
a[0] = a[19 % a[K]];
a[19] = b;
a = a.reverse();
a = a[he](2);
return a[O]("")
};
arguments.forEach(function(s) {
print(Qo(s));
});
保存为decrypt86.es
,用SpiderMonkey在本地执行:
$ chmod +x decrypt86.es
$ ./decrypt86.es 4A4A5F3E1E4317AB31BD81E1A155E39C01F8C8BD48.\
> 6EBE5DDC5720C003487C2B1927402C84DCBF0E56E56
A5F3E1E4317AB31BD81E1A155E39C01F8C8BD48.
EBE5DDC5720C003487C6B1927402C84DCBF0E52
如预期的那样,得到了正确的sig
。容易证明,该Qo()
函数实现的算法:
function decrypt86(s) {
s = s.split("");
s = s.reverse();
s = s.slice(3);
var t = s[0];
s[0] = s[19 % s.length];
s[19] = t;
s = s.reverse();
s = s.slice(2);
return s.join("")
}
与我们前面破解出的86位s
签名解密算法:(JavaScript版本)
function decrypt86(s) {
return s.substr(2, 61) + s.substr(82, 1) +
s.substr(64, 18) + s.substr(63, 1)
}
是完全等价的。如此,可以通过直接分析页面的JavaScript源码得到视频signature
的解密方法,省去了复杂耗时的破解过程。
YouTube视频签名加密算法的破解的更多相关文章
- youtube视频下载
开你的电脑,然后打开你的浏览器,浏览器可以是IE.Chrome.Firefox等等 在浏览器中输入这个网址:en.savefrom.net,点击Enter键,进入这个网页: 打开你需要下载的y ...
- YouTube视频代码总结
var tag = document.createElement('script'); tag.src = "https://www.youtube.com/iframe_api" ...
- 下载的youtube视频
youtube 视频下载方法[详解] 1.打开网址: http://kej.tw/flvretriever/. 2.输入要下载的youtube视频的网址. 3.点击右侧RETRIEVE NOW ! ...
- 在线Youtube视频下载,修改文本,剪切制作动画的最新方法
刚刚(减去编写本文章的时间,大概20分钟前吧)在看国外最新技术资讯的时候发现有个方法可以让我们快速去下载Youtube上面的视频,不敢独享,我自己都没有怎么玩就所以立刻post上来广而告之,希望对大家 ...
- python爬取youtube视频 多线程 非中文自动翻译
声明:我写的所有文章都是发在博客园的,我看到其他复制粘贴过去的 连个出处也不写,直接打上自己的水印...真是没的说了. 前言:前段时间搞了一些爬视频的项目,代码都写好了,这里写文章那就在来重新分析一遍 ...
- Python:使用youtube-dl+ffmpeg+FQ软件下载youtube视频
声明:本文所述内容都是从http://blog.csdn.net/u011475134/article/details/71023612博文中学习而来. 背景: 一同学想通过FQ软件下载一些youtu ...
- YouTube视频下载的12个软件(Win和Mac)
如今,观看视频已经成为人们生活中重要的一部分.很多时候,我们都需要用到视频,比如教育用途.会议报告.休闲娱乐以及广告宣传等.如果你觉得有时候资源不好找的话,不放去看下YouTube.YouTube是世 ...
- python下载youtube视频
谷歌开源了一个新的数据集,BoundingBox,(网址在这里)这个数据集是经过人工标注的视频数据集,自然想将它尽快地运用在实际之中,那么首先需要将其下载下来:可以看到网址上给出的是csv文件,该文件 ...
- Mac电脑如何快速下载YouTube视频
如果你想下载一些教育类的视频资源,或者是一些学习的教程,那么YouTube是一个很好的视频资源平台.YouTube上面各种各样的资源都有,而且质量都很有保证,尤其是那些订阅量很多的人.可惜的是,You ...
随机推荐
- 使用 sftp 向linux服务器传输文件
sftp是加密的文件传输. 登陆 sftp name@123.21.331.1 1 2.把本地文件name1传到服务器name2下 put /name1.html /name2/ 1 把服务器name ...
- C 标准库 - <locale.h>
C 标准库 - <locale.h> 简介 locale.h 头文件定义了特定地域的设置,比如日期格式和货币符号.接下来我们将介绍一些宏,以及一个重要的结构 struct lconv 和两 ...
- ASP.Net MVC开发基础学习笔记(8):新建数据页面
前言 前面解说了怎样创建一个查询页面并给查询页面加入排序.搜索及分页功能.今天我们来讲讲怎样向这个列表加入数据. 解说的顺序将依照加入数据的步骤的时间顺序来进行,方便大家理清逻辑关系. 本节将涉 ...
- hibernate的注解装配
1.多对多,(中间表不用映射) @ManyToMany @JoinTable(name = "中间表名", joinColumns = { @JoinColumn(name = & ...
- openCV2马拉松第18圈——坐标变换
计算机视觉讨论群162501053 转载请注明:http://blog.csdn.net/abcd1992719g 收入囊中 仿射变换 坐标映射 利用坐标映射做一些效果,例如以下 watermark/ ...
- 解决MySQL出现大量unauthenticated user的问题
近期OJ及相关的站点打开异常的慢,简直崩溃,一直没找着原因. 进入数据库server.进到mysql里,用show processlist命令查看一下,发现有非常多的unauthenticated u ...
- bootstrap table api
http://blog.csdn.net/rickiyeat/article/details/56483577
- EasyDarwin Streaming Server对Task的调用方法
我们在EasyDarwin流媒体服务器的二次开发过程中,经常会需要定义自己的Task类,例如在EasyDarwin中,RTSPSessioin.HTTPSession.RTCPTask等,都是Task ...
- Lombok引入简化Java代码
转载 http://t.cn/RS0UdrX Lombok简介 如Github上项目介绍所言,Lombok项目通过添加“处理程序”,使java成为一种更为简单的语言.作为一个Old Java Deve ...
- LeetCode算法题目解答汇总(转自四火的唠叨)
LeetCode算法题目解答汇总 本文转自<四火的唠叨> 只要不是特别忙或者特别不方便,最近一直保持着每天做几道算法题的规律,到后来随着难度的增加,每天做的题目越来越少.我的初衷就是练习, ...