weapi:逆向圈的"祖传教学案例"
网易云音乐网页版的 weapi 接口加密,可能是中文互联网被分析得最多的加密方案——因为它设计得实在太标准了:AES-CBC 加密业务数据(两遍),RSA 加密 AES 密钥。十年没变过,是练"JS 加密翻译 Python"的最佳教材。
加密流程完整拆解
POST 到 https://music.163.com/weapi/... 的数据只有两个参数:
params:业务数据两次 AES-CBC 加密的 base64encSecKey:AES 密钥经 RSA 加密后的 hex
完整实现:
from Crypto.Cipher import AES
import json, base64
# 三组常量,全部写死在前端 JS 里,十年没变
NONCE = "0CoJUm6Qyw8W8jud" # 第一次 AES 的固定 key
IV = "0102030405060708" # CBC 的固定 IV
PUBKEY = "010001" # RSA 公钥指数 e
MODULUS = ("00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7"
"b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280"
"104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932"
"575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b"
"3ece0462db0a22b8e7") # RSA 模数 n
def aes_cbc_encrypt(text, key):
pad = 16 - len(text.encode()) % 16
text += chr(pad) * pad # PKCS7 填充
cipher = AES.new(key.encode(), AES.MODE_CBC, IV.encode())
return base64.b64encode(cipher.encrypt(text.encode())).decode()
def rsa_encrypt(secret):
# 密钥先反转,转大整数,做 pow(secret, e, n)
n = int(secret[::-1].encode().hex(), 16)
return format(pow(n, int(PUBKEY, 16), int(MODULUS, 16)), 'x').zfill(256)
def weapi_encrypt(data: dict):
text = json.dumps(data)
secret = "aabbccddeeffgghh" # 16位随机串——但可以固定!服务端不校验随机性
params = aes_cbc_encrypt(aes_cbc_encrypt(text, NONCE), secret)
return {"params": params, "encSecKey": rsa_encrypt(secret)}设计点评:仪式感大于实际
从密码学角度审视这套方案,很有意思:
- RSA 公钥就写在前端,任何人都能加密——它保护的是"密钥传输过程的机密性",而不是"接口调用权限"。也就是说它防的是中间人窥探,不防协议模拟。
- AES 密钥可以固定写死。虽然设计上每次随机,但服务端只解密不校验随机性,
aabbccddeeffgghh用到天荒地老也没问题。 - 真正的防护在别处:登录态(MUSIC_U cookie)、频率限制、以及部分接口的"客户端类型"校验。
但对学习者它依然是完美教材:AES 模式判断、PKCS7 填充、RSA 无填充加密(textbook RSA)、大数运算,一个接口全练到。
进阶:eapi——App 接口的简化版
网易云的 App 接口走 eapi,加密方案和 weapi 不同但更简单,纯 AES-ECB:
def eapi_encrypt(url_path, data: dict):
text = json.dumps(data)
message = f"nobody{url_path}use{text}md5forencrypt"
digest = __import__('hashlib').md5(message.encode()).hexdigest()
payload = f"{url_path}-36cd479b6b5-{text}-36cd479b6b5-{digest}"
cipher = AES.new(b"e82ckenh8dichen8", AES.MODE_ECB)
pad = 16 - len(payload.encode()) % 16
payload += chr(pad) * pad
return cipher.encrypt(payload.encode()).hex().upper()请求时 url 里的 /api/ 换成 /eapi/,POST 参数只有一个 params(上面的 hex 大写)。eapi 的好处是接口权限更高(App 级),比如高品质音频接口只有 eapi 能过。
精髓:三个实战要点
- json 序列化格式影响密文但不会影响解密——服务端解开后按 json 解析,key 顺序无所谓。这点和签名类方案(如淘宝 mtop)完全不同,别搞混。
- MUSIC_U 是登录态核心,但它和账号、设备弱绑定,协议池里可以一号一 cookie 长期使用。掉登录的表现是接口返回
{"code": 301},不是签名错误。 - weapi 和 eapi 的接口路径映射:
/weapi/song/enhance/player/url↔/eapi/song/enhance/player/url,同一接口两套入口,weapi 被限就换 eapi,反之亦然。
总结
- weapi = 两遍 AES-CBC + RSA 加密密钥,常量全公开,实现一次终身受用
- eapi = AES-ECB + md5 校验串,固定 key
e82ckenh8dichen8,权限更高 - 这套方案防传输不防模拟,真正的门槛是登录态和限流
- 作为学习案例,它覆盖了对称/非对称加密的核心概念,值得手写一遍