百趣云 百趣云的博客

网易云音乐 weapi 加密:AES 套 RSA 的经典设计

weapi:逆向圈的"祖传教学案例"

网易云音乐网页版的 weapi 接口加密,可能是中文互联网被分析得最多的加密方案——因为它设计得实在太标准了:AES-CBC 加密业务数据(两遍),RSA 加密 AES 密钥。十年没变过,是练"JS 加密翻译 Python"的最佳教材。

加密流程完整拆解

POST 到 https://music.163.com/weapi/... 的数据只有两个参数:

  • params:业务数据两次 AES-CBC 加密的 base64
  • encSecKey: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)}

设计点评:仪式感大于实际

从密码学角度审视这套方案,很有意思:

  1. RSA 公钥就写在前端,任何人都能加密——它保护的是"密钥传输过程的机密性",而不是"接口调用权限"。也就是说它防的是中间人窥探,不防协议模拟。
  2. AES 密钥可以固定写死。虽然设计上每次随机,但服务端只解密不校验随机性,aabbccddeeffgghh 用到天荒地老也没问题。
  3. 真正的防护在别处:登录态(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 能过。

精髓:三个实战要点

  1. json 序列化格式影响密文但不会影响解密——服务端解开后按 json 解析,key 顺序无所谓。这点和签名类方案(如淘宝 mtop)完全不同,别搞混。
  2. MUSIC_U 是登录态核心,但它和账号、设备弱绑定,协议池里可以一号一 cookie 长期使用。掉登录的表现是接口返回 {"code": 301},不是签名错误。
  3. weapi 和 eapi 的接口路径映射/weapi/song/enhance/player/url/eapi/song/enhance/player/url,同一接口两套入口,weapi 被限就换 eapi,反之亦然。

总结

  1. weapi = 两遍 AES-CBC + RSA 加密密钥,常量全公开,实现一次终身受用
  2. eapi = AES-ECB + md5 校验串,固定 key e82ckenh8dichen8,权限更高
  3. 这套方案防传输不防模拟,真正的门槛是登录态和限流
  4. 作为学习案例,它覆盖了对称/非对称加密的核心概念,值得手写一遍
By 百趣云 阅读量:1 On