密码学方法
多年以前,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 imes 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