优酷用户、视频ID加解密
目录
优酷作为国内一大在线视频网站,无聊的时候上去看看总能发现一些有趣的视频。然而煲剧也好,看片也罢,我们应该始终遵循我们的口号:搞事!搞事!!搞事!!!这不又碰巧发现了一些有趣的事情……
作为一只程序猿,显然你的注意力不应该仅仅集中在视频本身:
- 第一眼,瞄一下浏览器地址栏(http://v.youku.com/v_show/id_XMjgxMjkxNjc0MA==.html,没错就是王尼玛!);
- 第二眼,瞄一下网页源代码;
显然,每个视频都有一个ID(比如:id_XMjgxMjkxNjc0MA==),同时也有另一个ID:703229185(在网页源代码中可以找到该videoId)。凭直觉,这两个ID之间存在着某种可逆的计算过程!
同时,继我们上次的爬虫抓取 http://www.youku.com/u/channelRanking 页面,我们发现每一个用户ID也有类似的加解密。
数字ID | 字符串ID |
138846849 | http://i.youku.com/i/UNTU1Mzg3Mzk2 |
327921687 | http://i.youku.com/i/UMTMxMTY4Njc0OA== |
680184272 | http://v.youku.com/v_show/id_XMjcyMDczNzA4OA== |
410086641 | http://v.youku.com/v_show/id_XMTY0MDM0NjU2NA== |
源数据
为了进行下一步的工作,我们需要先获取了一批样本数据用于后续的分析,这里用用户ID为例:
eid | uid |
UNzAzNg== | 1759 |
UMTA3MDg= | 2677 |
UODk2ODA= | 22420 |
UMTAwMDU1Ng== | 250139 |
UNDA0MzcxNg== | 1010929 |
UMTAwMDgzNjg= | 2502092 |
UNzkwOTQ1NDA= | 19773635 |
UMzM3NTIyNjg0MA== | 843806710 |
分析
第一步:base64decode,发现随着uid的增大,eid长度也在增大,但两者的位数却无法一一对应:
eid | eid_b64decode | len(eid_b64decode) | uid | len(uid) |
UNzAzNg== | ‘P\xdc\xc0\xcc\xd8’ | 5 | 1759 | 4 |
UMTA3MDg= | ‘P\xc4\xc0\xdc\xc0\xe0’ | 6 | 2677 | 4 |
UODk2ODA= | ‘P\xe0\xe4\xd8\xe0\xc0’ | 6 | 22420 | 5 |
UMTAwMDU1Ng== | ‘P\xc4\xc0\xc0\xc0\xd4\xd4\xd8’ | 8 | 250139 | 6 |
UNDA0MzcxNg== | ‘P\xd0\xc0\xd0\xcc\xdc\xc4\xd8’ | 8 | 1010929 | 7 |
UMTAwMDgzNjg= | ‘P\xc4\xc0\xc0\xc0\xe0\xcc\xd8\xe0’ | 9 | 2502092 | 7 |
UNzkwOTQ1NDA= | ‘P\xdc\xe4\xc0\xe4\xd0\xd4\xd0\xc0’ | 9 | 19773635 | 8 |
UMzM3NTIyNjg0MA== | ‘P\xcc\xcc\xdc\xd4\xc8\xc8\xd8\xe0\xd0\xc0’ | 11 | 843806710 | 9 |
第二步:由于uid是纯数字,考虑将 eid_b64decode 以数字的形式展现出来:
eid | eid_b64decode | eid_b64decode_num | uid |
UNzAzNg== | ‘P\xdc\xc0\xcc\xd8’ | 220-192-204-216 | 1759 |
UMTA3MDg= | ‘P\xc4\xc0\xdc\xc0\xe0’ | 196-192-220-192-224 | 2677 |
UODk2ODA= | ‘P\xe0\xe4\xd8\xe0\xc0’ | 224-228-216-224-192 | 22420 |
UMTAwMDU1Ng== | ‘P\xc4\xc0\xc0\xc0\xd4\xd4\xd8’ | 196-192-192-192-212-212-216 | 250139 |
UNDA0MzcxNg== | ‘P\xd0\xc0\xd0\xcc\xdc\xc4\xd8’ | 208-192-208-204-220-196-216 | 1010929 |
UMTAwMDgzNjg= | ‘P\xc4\xc0\xc0\xc0\xe0\xcc\xd8\xe0’ | 196-192-192-192-224-204-216-224 | 2502092 |
UNzkwOTQ1NDA= | ‘P\xdc\xe4\xc0\xe4\xd0\xd4\xd0\xc0’ | 220-228-192-228-208-212-208-192 | 19773635 |
UMzM3NTIyNjg0MA== | ‘P\xcc\xcc\xdc\xd4\xc8\xc8\xd8\xe0\xd0\xc0’ | 204-204-220-212-200-200-216-224-208-192 | 843806710 |
- 将各组数据分别进行简单的算术运算,发现仍然无法得到uid;
- 猜测:aXn + bXn-1 + cXn-2 + … + yXn + z 形式,计算比较麻烦,但最后结果仍然不对;
第三步:留意到 eid_b64decode 的每一位出现的频率,推测其为枚举值,故统计多组eid-uid计算其分布:
位 | 出现次数 |
‘\xc0’ | 1590 |
‘\xc4’ | 1920 |
‘\xc8’ | 1788 |
‘\xcc’ | 1715 |
‘\xd0’ | 1557 |
‘\xd4’ | 1163 |
‘\xd8’ | 1580 |
‘\xdc’ | 1137 |
‘\xe0’ | 1562 |
‘\xe4’ | 1163 |
发现枚举为10个值,推测其分别对应0~9。
第四步:以0~9分别替代上面的字符:
eid | eid_b64decode | eid_num | uid |
UNzAzNg== | ‘P\xdc\xc0\xcc\xd8’ | 7036 | 1759 |
UMTA3MDg= | ‘P\xc4\xc0\xdc\xc0\xe0’ | 10708 | 2677 |
UODk2ODA= | ‘P\xe0\xe4\xd8\xe0\xc0’ | 89680 | 22420 |
UMTAwMDU1Ng== | ‘P\xc4\xc0\xc0\xc0\xd4\xd4\xd8’ | 1000556 | 250139 |
UNDA0MzcxNg== | ‘P\xd0\xc0\xd0\xcc\xdc\xc4\xd8’ | 4043716 | 1010929 |
UMTAwMDgzNjg= | ‘P\xc4\xc0\xc0\xc0\xe0\xcc\xd8\xe0’ | 10008368 | 2502092 |
UNzkwOTQ1NDA= | ‘P\xdc\xe4\xc0\xe4\xd0\xd4\xd0\xc0’ | 79094540 | 19773635 |
UMzM3NTIyNjg0MA== | ‘P\xcc\xcc\xdc\xd4\xc8\xc8\xd8\xe0\xd0\xc0’ | 3375226840 | 843806710 |
发现 eid_num 与 uid 存在 × 4倍关系。
第五步:数据校验,部分数据发现其虽然形式类似 base64,但却无法decode:
UMjc4MTA0MDM2 | 69526009 |
UMTMyNTE3MTcy | 33129293 |
UNTEwMTgyMzY0 | 127545591 |
UMzIzOTM0NTAw | 80983625 |
UNDkwNzkyODY4 | 122698217 |
统计大量数据后,发现其末位字符为 “wy024” 时无法decode。
进一步发现,”wy024″情况下需要进行补位,其对应关系为:
末位 | 最后结果补位 |
w | 0 |
y | 2 |
0 | 4 |
2 | 6 |
4 | 8 |
至此,分析过程完成。
实现
#!/usr/bin/env python # -*- coding: utf-8 -*- import base64 char = "\xc0\xc4\xc8\xcc\xd0\xd4\xd8\xdc\xe0\xe4" char_map = {char[i]: str(i) for i in range(len(char))} suffix = "wy024" suffix_map = {suffix[i]: str(i*2) for i in range(len(suffix))} def uid2eid(uid): """ 用户数字ID 加密为 字符串ID""" return encrypt(uid, prefix="P") def uid2vid(uid): """ 视频数字ID 加密为 字符串ID """ return encrypt(uid, prefix="\\") def eid2uid(eid): """ 字符串ID 解密为 数字ID """ # 判断最后一位是否需要进行fix fix = suffix_map[eid[-1]] if eid[-1] in suffix else "" # 去掉最后一位 eid = eid[:-1] ss = base64.b64decode(eid)[1:] num = [] for s in ss: num.append(char_map[s]) num.append(fix) uid = int("".join(num)) / 4 return uid def encrypt(id, prefix): num = str(int(id)*4) fix = "=" if len(num) % 3 == 0: fix = suffix[int(num[-1])/2] num = num[:-1] ss = prefix for s in str(num): ss += char[int(s)] eid = base64.b64encode(ss) + fix return eid
结尾
当然,在实际分析过程中,也走了一些弯路,分析过程并非从头到尾都很顺畅。另外,最后对数据进行校验,其实也才逐步发现id在加密时的特殊处理等。
评论