918 字
2 分钟
扣扣音乐双参数纯算
某音乐接口加密算法逆向分析
一、目标概述
本文通过对扣扣音乐Web端请求加密机制的深入分析,揭示了其核心加密算法的实现原理。分析对象为该页面发送的API请求。
二、请求结构分析
2.1 请求参数
通过Chrome DevTools抓包分析,发现QQ音乐API请求包含以下关键参数:
| 参数名 | 说明 | 示例值 |
|---|---|---|
_ | 时间戳(毫秒) | 1781411109906 |
encoding | 编码方式 | ag-1(表示加密) |
sign | 请求签名 | zzc23c81b08ln0xcliusuovjw58qfbye8cy19660417 |
2.2 请求体格式
请求体为Base64编码的加密数据,服务器响应同样为加密的二进制数据。
三、Sign签名算法分析
3.1 算法流程
根据插桩日志分析,签名生成流程如下:
- SHA-1哈希:对JSON请求数据进行SHA-1哈希,结果转换为大写十六进制字符串
- 索引提取:从哈希结果中按固定索引提取字符
- XOR混淆:将哈希字节与固定密钥进行异或操作
- Base64编码:对异或结果进行Base64编码
- 格式拼接:组合前缀、索引字符串和Base64结果




整体流程

3.2 核心代码实现
function get_sign(data) { // 1. SHA-1 哈希(大写) data = JSON.stringify(data); const hash = crypto.createHash('sha1').update(data).digest('hex').toUpperCase();
// 2. 固定索引提取 const idx1 = [23, 14, 6, 36, 16, 40, 7, 19]; const idx2 = [16, 1, 32, 12, 19, 27, 8, 5]; const str1 = idx1.map(i => hash[i]).join(''); const str2 = idx2.map(i => hash[i]).join('');
// 3. 固定密钥(20字节,对应SHA-1输出长度) const key = [89, 39, 179, 150, 218, 82, 58, 252, 177, 52, 186, 123, 120, 64, 242, 133, 143, 161, 121, 179];
// 4. 哈希字符串转字节数组 const hashBytes = []; for (let i = 0; i < hash.length; i += 2) { hashBytes.push(parseInt(hash.substring(i, i + 2), 16)); }
// 5. XOR操作 const xorResult = hashBytes.map((byte, i) => byte ^ key[i]);
// 6. Base64编码并移除末尾等号 const xorBuffer = Buffer.from(xorResult); const base64Sign = xorBuffer.toString('base64').replace(/=+$/, '');
// 7. 组合结果(前缀zzc + str1 + base64 + str2) return ("zzc" + str1 + base64Sign + str2).toLowerCase();}3.3 索引数组来源
插桩日志显示,索引数组 [23, 14, 6, 36, 16, 40, 7, 19] 来自域名检测逻辑:
[53535] [obj[key] = value类型] obj: [null,null,null,null,null,null,null,null] key:[0] value: 23[53537] [obj[key] = value类型] obj: [23,null,null,null,null,null,null,null] key:[1] value: 14[53538] [obj[key] = value类型] obj: [23,14,null,null,null,null,null,null] key:[2] value: 6...该数组通过检测当前页面域名 y.qq.com 是否在白名单 ['qq.com', 'joox.com', 'tencentmusic.com', ...] 中生成,是一种简单的环境指纹机制。
四、请求体加密算法(AES-GCM)
日志分析



4.1 算法识别
从表单插桩日志可以清晰看到加密流程:
// 插桩日志关键信息提取// [11] new Uint8Array([189, 48, 95, 16, 208, 255, 116, 182, 239, 84, 218, 184, 53, 181, 225, 207]) - 密钥// [12] new Uint8Array(12) - IV容器// [13] crypto.getRandomValues(iv) - 生成随机IV// [14] importKey() - 导入密钥// [19] encrypt() - AES-GCM加密4.2 加密流程
┌─────────────────────────────────────────────────────────────┐│ AES-GCM 加密流程 │├─────────────────────────────────────────────────────────────┤│ 1. JSON序列化 → 将请求对象转为JSON字符串 ││ 2. UTF-8编码 → TextEncoder.encode() ││ 3. 生成随机IV → crypto.getRandomValues(12字节) ││ 4. AES-GCM加密 → SubtleCrypto.encrypt() ││ 5. 数据拼接 → IV(12) + 密文 + AuthTag(16) ││ 6. Base64编码 → btoa() 或 Buffer.toString('base64') │└─────────────────────────────────────────────────────────────┘4.3 核心代码实现
function encryptSync(data) { // 固定密钥(16字节 = 128位) const key = Buffer.from([189, 48, 95, 16, 208, 255, 116, 182, 239, 84, 218, 184, 53, 181, 225, 207]);
// 随机IV(12字节,GCM推荐长度) const iv = crypto.randomBytes(12);
// 创建AES-GCM加密器 const cipher = crypto.createCipheriv('aes-128-gcm', key, iv);
// 支持对象或字符串输入 const jsonStr = typeof data === 'string' ? data : JSON.stringify(data);
// 执行加密 const encrypted = Buffer.concat([ cipher.update(jsonStr, 'utf8'), cipher.final() ]);
// 获取认证标签(16字节) const authTag = cipher.getAuthTag();
// 拼接格式:IV + 密文 + AuthTag const cipherWithTag = Buffer.concat([iv, encrypted, authTag]);
// Base64编码输出 return cipherWithTag.toString('base64');}4.4 数据格式说明
| 数据段 | 长度 | 说明 |
|---|---|---|
| IV | 12字节 | 初始化向量,每次加密随机生成 |
| 密文 | 可变 | AES-GCM加密结果 |
| AuthTag | 16字节 | 认证标签,用于完整性校验 |
五、响应解密算法
5.1 解密入口定位
在 vendor.chunk.js 中定位到解密函数:
var ae = oe.__cgiEncrypt, // 加密函数 se = oe.__cgiDecrypt; // 解密函数(delete oe.__cgiEncrypt, delete oe.__cgiDecrypt); // 执行后立即删除5.2 解密流程
响应解密使用与加密相同的密钥和算法:
function decryptResponse(encryptedData) { const key = Buffer.from([189, 48, 95, 16, 208, 255, 116, 182, 239, 84, 218, 184, 53, 181, 225, 207]);
// Base64解码(如果是字符串) if (typeof encryptedData === 'string') { encryptedData = Buffer.from(encryptedData, 'base64'); }
// 解析数据结构 const iv = encryptedData.slice(0, 12); // 前12字节:IV const authTag = encryptedData.slice(-16); // 后16字节:AuthTag const ciphertext = encryptedData.slice(12, -16); // 中间:密文
// 创建解密器并解密 const decipher = crypto.createDecipheriv('aes-128-gcm', key, iv); decipher.setAuthTag(authTag);
const plaintext = Buffer.concat([ decipher.update(ciphertext), decipher.final() ]);
return plaintext.toString('utf8');}六、完整请求流程
┌─────────────────────────────────────────────────────────────────────────┐│ QQ音乐API请求完整流程 │├─────────────────────────────────────────────────────────────────────────┤│ ││ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ││ │ 构造请求数据 │───▶│ 生成Sign签名 │───▶│ AES-GCM加密 │ ││ │ (JSON) │ │ (SHA-1+XOR) │ │ (固定密钥) │ ││ └──────────────┘ └──────────────┘ └──────┬───────┘ ││ │ ││ ▼ ││ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ││ │ 发送HTTP请求│◀───│ Base64编码 │◀───│ IV+密文+Tag │ ││ └──────┬───────┘ └──────────────┘ └──────────────┘ ││ │ ││ ▼ ││ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ ││ │ 接收响应 │───▶│ AES-GCM解密 │───▶│ JSON解析 │ ││ │ (加密数据) │ │ (固定密钥) │ │ (业务数据) │ ││ └──────────────┘ └──────────────┘ └──────────────┘ ││ │└─────────────────────────────────────────────────────────────────────────┘七、关键发现总结
7.1 加密参数汇总
| 参数 | 值 | 说明 |
|---|---|---|
| 加密算法 | AES-128-GCM | 认证加密模式 |
| 密钥 | [189, 48, 95, 16, 208, 255, 116, 182, 239, 84, 218, 184, 53, 181, 225, 207] | 固定128位密钥 |
| IV长度 | 12字节 | GCM推荐长度 |
| AuthTag长度 | 16字节 | 完整性校验 |
| 签名算法 | SHA-1 + XOR + Base64 | 多层混淆 |
7.2 安全评估
优点:
- 使用标准AES-GCM算法,提供机密性和完整性保障
- IV随机生成,避免相同明文产生相同密文
缺陷:
- 密钥硬编码在前端代码中,可通过逆向分析获取
- Sign签名密钥同样固定,缺乏动态性
- 未使用HTTPS之外的额外通道加密
7.3 绕过策略
由于密钥和算法均已完全还原,可直接在Node.js环境中模拟完整请求流程:
import requestsimport execjs
# 使用Node.js执行加密with open('sign.js', 'r', encoding='utf-8') as f: sig = execjs.compile(f.read())
data = {"comm": {...}, "req_1": {...}}sign = sig.call("get_sign", data)encrypted_data = sig.call("encryptSync", data)
# 发送请求response = requests.post(url, params={"sign": sign, "encoding": "ag-1"}, data=encrypted_data)
# 解密响应decrypted = sig.call("decryptSync", response.content)八、总结
本文通过插桩日志分析,完整还原了QQ音乐Web端的API加密机制:
- Sign签名:采用SHA-1哈希+固定索引提取+XOR混淆+Base64编码的组合方式
- 请求体加密:使用固定密钥的AES-GCM算法,随机IV保证安全性
- 响应解密:使用相同密钥逆向解密
该加密方案在前端实现,存在密钥暴露风险,但通过混淆手段增加了逆向难度。对于爬虫开发者而言,只需提取固定密钥即可完整模拟请求。
部分信息可能已经过时









