1. 从点击关注到发现加密参数
最近在研究某查查网的关注功能时,发现它的API请求头里有两个神秘的参数:key和val。每次请求时这两个值都会变化,明显是某种动态加密的结果。作为一个喜欢刨根问底的技术人,我决定深入探究一下这个加密机制。
打开Chrome开发者工具,切换到Network面板,勾选Preserve log选项。登录某查查网后,随便搜索一家公司点击关注,在XHR请求中果然看到了这个带着加密参数的请求。仔细观察发现,这两个参数并不是简单的随机字符串,而是有规律的加密结果。
为了找到加密逻辑的源头,我在XHR请求上设置了断点。点击关注按钮触发断点后,通过Call Stack面板逐层向上追踪调用栈。这个过程就像侦探破案一样,需要耐心地一层层排查。最终在某个异步回调函数中发现了关键线索 - 这里调用了f.request方法,而加密参数的生成就在这个调用链中。
2. 逆向分析加密逻辑
2.1 定位关键加密函数
通过断点调试,我发现加密过程主要分为两个部分:key的生成和val的生成。这两个值都使用了HmacSHA512加密算法,但输入的参数有所不同。
key的生成流程是这样的:
- 将API接口路径转换为小写
- 将路径字符串重复拼接两次
- 对每个字符的Unicode码进行特定运算
- 通过一个固定的映射表生成中间密钥
- 用这个密钥对API路径和请求体数据进行HmacSHA512加密
- 最后取加密结果的第8到28位作为最终的key
// 类似这样的代码逻辑 function generateKey(apiPath, requestData) { const codeMap = { /* 省略映射表 */ }; let tempKey = ''; const doublePath = apiPath.toLowerCase() + apiPath.toLowerCase(); for(let char of doublePath) { const code = char.charCodeAt(0); const index = code % Object.keys(codeMap).length; tempKey += codeMap[index]; } // 后续进行HmacSHA512加密... }2.2 解密val的生成过程
val的生成比key更复杂一些,除了API路径和请求数据外,还引入了一个额外的tid参数。这个tid是从网页的全局变量window.tid中获取的,每次页面加载都会变化。
具体步骤:
- 拼接API路径、"pathString"、请求体数据和tid
- 使用与key相同的密钥生成方法
- 对拼接后的字符串进行HmacSHA512加密
- 取完整加密结果作为val
# Python示例代码片段 def generate_val(api_path, data, tid): data_str = json.dumps(data, separators=(',', ':')).lower() combined = api_path + "pathString" + data_str + tid # 后续加密逻辑...3. 完整Python实现
3.1 构建加密工具函数
经过上面的分析,我们可以用Python完整复现这个加密过程。首先需要准备两个核心函数:
import hashlib import hmac import json def hmac_sha512(key, data): """HmacSHA512加密函数""" hmac_obj = hmac.new(key.encode(), data.encode(), digestmod=hashlib.sha512) return hmac_obj.hexdigest() def generate_codes(api_path): """生成加密用的中间密钥""" code_map = { "0": "W", "1": "l", "2": "k", "3": "B", "4": "Q", "5": "g", "6": "f", "7": "i", "8": "i", "9": "r", "10": "v", "11": "6", "12": "A", "13": "K", "14": "N", "15": "k", "16": "4", "17": "L", "18": "1", "19": "8" } lower_path = api_path.lower() double_path = lower_path + lower_path codes = '' for char in double_path: code = ord(char) % len(code_map) codes += code_map[str(code)] return codes3.2 组装完整请求头
有了基础函数后,就可以实现完整的请求头生成了:
def generate_headers(api_path, request_data, tid): """生成加密请求头""" # 准备数据 data_str = json.dumps(request_data, separators=(',', ':')).lower() # 生成key key_data = api_path + data_str temp_key = generate_codes(api_path) header_key = hmac_sha512(temp_key, key_data).lower()[8:28] # 生成val val_data = api_path + "pathString" + data_str + tid temp_val_key = generate_codes(api_path) header_val = hmac_sha512(temp_val_key, val_data) return {header_key: header_val} # 使用示例 if __name__ == '__main__': api = "/api/user/addfollowinglist" data = {'companyKeyno': "6e9104e7983f72a2178aa189a1ab8d54"} tid = "2727b92fa87a73d7fe2d4bef8324ea61" # 需要从网页中获取 headers = generate_headers(api, data, tid) print(headers)4. 实战中的注意事项
4.1 如何获取tid参数
tid参数是这个加密体系中的重要一环,它存储在网页的全局变量window.tid中。在实际应用中,我们需要先访问网页获取这个值。可以通过以下方式:
- 使用requests库先请求首页
- 用正则表达式或HTML解析器提取tid
- 将tid用于后续的API请求
import re import requests def get_tid(): response = requests.get('https://www.qcc.com/') tid_match = re.search(r'window\.tid\s*=\s*["\']([^"\']+)["\']', response.text) if tid_match: return tid_match.group(1) raise Exception("Failed to extract tid")4.2 处理动态变化的参数
在实际使用中发现,除了tid会变化外,某些API的请求参数也可能包含时间戳或随机数。这就需要我们:
- 仔细分析每个API的请求参数
- 识别哪些是固定值,哪些是动态生成的
- 对动态参数找到其生成规律或获取方式
4.3 调试技巧分享
在逆向过程中,我总结了一些实用的调试技巧:
- 使用console.log在关键位置输出变量值
- 结合断点和单步执行逐步跟踪代码
- 注意观察闭包中的变量,它们常常存储重要信息
- 对于混淆的代码,可以尝试用AST工具进行反混淆
5. 加密算法深度解析
5.1 HmacSHA512的工作原理
HmacSHA512是一种基于SHA-512哈希算法的消息认证码。它的特点是:
- 需要密钥和数据两个输入
- 通过特定的填充和迭代方式增强安全性
- 输出固定长度的512位(64字节)哈希值
- 即使输入微小变化,输出也会完全不同
在我们的案例中,网站开发者对标准HmacSHA512做了二次加工,取了其中部分字符作为最终结果。
5.2 自定义编码表的妙用
观察代码中的code_map,可以发现它有几个特点:
- 长度固定为20个字符
- 映射关系看似随机但其实是固定的
- 通过Unicode码取余的方式决定使用哪个字符
- 这种设计增加了逆向难度,因为需要同时知道映射表和算法
这种自定义编码表在Web安全中很常见,它相当于在标准加密算法外又加了一层保护。
6. 扩展应用与思考
6.1 如何应对算法变更
在实际项目中,网站可能会不定期更新加密算法。为了应对这种情况,可以:
- 定期检查加密是否仍然有效
- 建立自动化的算法检测机制
- 保留历史版本的处理逻辑
- 设计可插拔的加密模块
6.2 其他网站的类似加密
很多网站都采用了类似的动态请求头加密方案,常见的变化包括:
- 使用不同的哈希算法(如MD5、SHA256等)
- 添加时间戳或随机数作为盐值
- 多层加密嵌套
- 结合WebAssembly实现核心加密逻辑
掌握了这个案例的分析方法后,可以举一反三应用到其他网站的逆向工程中。