悠悠楠杉
某保险资讯App请求头token与sign加密算法逆向分析
本文通过完整的逆向工程流程,深度解析某保险资讯App的请求头加密机制,包括token动态生成策略与sign签名算法实现原理,提供详细的算法还原方法与代码实现。
一、初探请求头加密特征
在使用Charles抓包分析该App的HTTPS流量时,发现所有业务请求头都包含两个关键参数:
http
token: 0xFA8B2C...(32位十六进制字符串)
sign: 9a7f45...(40位SHA1哈希)
通过连续抓包对比发现三个特征:
1. token在用户登录后固定不变
2. sign随每次请求发生变化
3. 未携带参数时sign仍会生成
二、动态调试定位加密点
2.1 环境准备
- 真机:Redmi Note 11 Pro(Android 12)
- 工具:Frida + Jadx + IDA Pro
2.2 关键定位过程
通过拦截okhttp3请求框架,发现最终调用链:
java
com.insurance.secure.util.SecurityHelper
├─ generateToken() // 设备指纹生成
└─ generateSign(String param) // 动态签名生成
使用Frida主动调用验证:
javascript
Interceptor.attach(Module.findExportByName("libnative.so", "Java_com_insurance_secure_util_SecurityHelper_generateSign"), {
onEnter: function(args) {
console.log("Sign Params:", Java.vm.getEnv().getStringUtfChars(args[2], null).readCString());
}
});
三、token生成算法还原
逆向分析发现token由三部分组成:
0x[设备指纹][时间戳][CRC校验码]
具体实现代码:
python
def generate_token():
android_id = get_system_property("ro.serialno") # 取设备序列号
timestamp = int(time.time() * 1000)
raw_token = f"{android_id}|{timestamp}"
crc32 = binascii.crc32(raw_token.encode()) & 0xFFFFFFFF
return f"0x{android_id[:8]}{timestamp:x}{crc32:08x}"
关键点:
- 使用设备硬件信息保证唯一性
- 未登录时生成临时token
- 登录后服务端返回持久化token
四、sign签名算法深度解析
通过Hook发现sign生成包含5个步骤:
- 参数标准化java
// 示例请求参数
{
"page": 1,
"size": 20,
"timestamp": 1659345678123
}
// 处理规则:
// 1. 按key字母排序
// 2. 过滤value为null的字段
// 3. 用=连接key-value,&连接不同字段
→ "page=1&size=20×tamp=1659345678123"
拼接固定盐值
python salt = "@#Insurance$2023!" raw_sign = sorted_params + salt
双重哈希处理
c // 伪代码实现 MD5(md5(raw_sign).substr(8,16) + token)
大小写转换
将结果字符串中第3、7、11位字符转为大写最终编码
python final_sign = base64_encode(sha1(processed_str)).substr(0,40)
五、算法安全性与对抗建议
该加密方案存在三处可优化点:
1. 盐值硬编码风险:so文件中静态字符串可通过字符串搜索定位
2. 设备指纹伪造:通过Xposed模块可伪造android_id等参数
3. 签名时效缺失:未绑定请求时间有效性校验
企业级加固建议:
- 采用动态盐值下发机制
- 增加请求时间窗口验证
- 关键算法使用白盒加密实现
六、完整实现代码
Python版签名生成器:python
import hashlib
import base64
def generatesign(params: dict, token: str): # 参数排序处理 sortedparams = '&'.join([f"{k}={v}" for k,v in sorted(params.items()) if v])
# 拼接盐值
SALT = "@#Insurance$2023!"
raw_str = sorted_params + SALT
# 双重哈希
stage1 = hashlib.md5(raw_str.encode()).hexdigest()[8:24]
stage2 = hashlib.md5((stage1 + token).encode()).hexdigest()
# 大小写转换
sign_list = list(stage2)
for pos in [2,6,10]:
sign_list[pos] = sign_list[pos].upper()
# 最终编码
return base64.b64encode(
hashlib.sha1(''.join(sign_list).encode()).digest()
).decode()[:40]