优酷用户、视频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在加密时的特殊处理等。

评论