app端header签名失效是因为其为基于时间戳、随机数、请求体、密钥等动态参数实时计算的hmac-sha256或aes-cbc结果,并非固定字符串;硬编码抓包值必然失败,需通过frida hook还原真实key/data/iv输入并用pycryptodome精准复现拼接与加解密逻辑。

为什么直接复制App的Header签名会失效
App端签名通常不是固定字符串,而是基于时间戳、随机数、请求体、密钥等动态参数实时计算的HMAC-SHA256或AES-CBC结果。抓包看到的X-Sign或Signature字段每次都不一样,硬编码必然失败。
关键点在于:签名逻辑往往藏在so库或混淆后的Java/Kotlin代码里,但多数情况下,核心加密流程仍依赖标准算法——PyCryptodome能覆盖绝大多数场景,前提是还原出正确的输入拼接方式和密钥处理逻辑。
- 常见错误现象:
Signature verification failed或401 Unauthorized,即使时间戳、nonce、body都对得上 - 根本原因:密钥被二次处理(如base64解码后再取前16字节)、原始数据未按约定排序/拼接/编码(比如要求UTF-8 bytes但用了str)、缺少预置IV或padding方式不匹配
- 调试建议:用Frida Hook住签名函数,打印出实际传入的
key、data、iv三元组,这是最可靠的输入依据
用PyCryptodome实现HMAC-SHA256签名(最常见类型)
多数App用HMAC-SHA256生成签名,密钥通常是硬编码在APK里的字符串,但可能经过base64.b64decode()或bytes.fromhex()转换。
注意:HMAC不要求IV,但必须严格对齐密钥长度(SHA256推荐32字节密钥)。若原始密钥不足32字节,有些App会用PKCS#7 padding补足,更多情况是直接截断或重复填充——需逆向确认。
from hashlib import sha256
from hmac import HMAC
<h1>假设逆向得到原始密钥为 base64 字符串,且需解码</h1><p>raw_key = "dGhpcyBpcyBhIHNlY3JldA=="
key = base64.b64decode(raw_key)</p><h1>拼接规则示例:method + \n + path + \n + timestamp + \n + nonce + \n + body_md5</h1><p>message = f"POST\n/api/v1/user\n1717023456\nabc123\n{md5(body.encode()).hexdigest()}".encode()</p><div class="aritcle_card flexRow artxards">
<div class="artcardd flexRow">
<a class="aritcle_card_img" rel="nofollow" href="/xiazai/skill8910" title="361SCAN security check your skill python scripts"><img
src="https://img.php.cn/upload/skill/000/000/081/179231049778675.jpg" alt="361SCAN security check your skill python scripts" onerror="this.onerror='';this.src='/static/lhimages/moren/morentu.png'" ></a>
<div class="aritcle_card_info flexColumn">
<a rel="nofollow" href="/xiazai/skill8910" title="361SCAN security check your skill python scripts" class="overflowclass">361SCAN security check your skill python scripts</a>
<p class="overflowclass">扫描并分析已安装技能,用于查看指定技能目录的名称、描述和详情,或列出所有已安装的技能。</p>
</div>
<a rel="nofollow" href="/xiazai/skill8910" title="361SCAN security check your skill python scripts" class="aritcle_card_btn flexRow flexcenter"><b></b><span>下载</span>
</a>
</div>
</div><p>signature = HMAC(key, message, sha256).hexdigest()
headers["X-Sign"] = signature
</p>
- 务必检查App是否对
body做了MD5(或SHA1)哈希后再参与拼接;有些只取body非空时的原始bytes - 时间戳单位要一致(秒 or 毫秒),且服务端常有5分钟容错窗口,本地时间偏差过大会直接拒签
- Python 3.9+ 的
HMAC默认接受bytes,传str会报TypeError: key: expected bytes or bytearray, but got 'str'
AES-CBC签名(带IV的加密型签名)
少数App把拼接后的字符串用AES-CBC加密再hex,作为签名。此时必须还原出IV——它可能是固定值(如b"0102030405060708"),也可能是从请求头或响应中动态提取的字段(如X-Nonce前8字节)。
PyCryptodome要求明文长度是16字节整数倍,PKCS#7是最常用padding方式。别用pad(data, 16)这种模糊写法,必须显式调用PKCS7模块或手写补位逻辑。
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad
<p>key = b"your-32-byte-key-here-123456789012"
iv = b"0102030405060708" # 必须16字节
data = "timestamp=1717023456&nonce=abc123&body={}".format(body).encode()</p><p>cipher = AES.new(key, AES.MODE_CBC, iv)
encrypted = cipher.encrypt(pad(data, AES.block_size))
signature = encrypted.hex()
headers["X-Sign"] = signature
</p>
- 如果服务端返回
Invalid IV或解密乱码,优先怀疑IV来源不对;可用Wireshark对比真实App请求中的X-IV或类似字段 - 某些App会把AES输出再做一次base64或反转字节序(
encrypted[::-1]),这类变形必须靠Hook日志确认 - PyCryptodome安装命令是
pip install pycryptodome,不是pycrypto(已废弃且不兼容)
签名验证失败时的快速定位路径
当签名始终不通过,不要反复改密钥长度或算法——先确认输入是否完全一致。真实App签名函数的输入,永远比你从代码里猜出来的更“脏”。
- 用Frida Hook
javax.crypto.Mac.getInstance("HmacSHA256")或javax.crypto.Cipher.getInstance("AES/CBC/PKCS7Padding"),打印doFinal()前的input和key - 对比Python与App端的
message十六进制dump(message.hex()),一个换行符、一个空格、一个URL编码差异都会导致签名不同 - 服务端有时会校验
User-Agent或X-App-Version,这些虽不参与签名,但缺失会导致整个请求被拦截,误判为签名错误
逆向签名逻辑最难的从来不是算法本身,而是搞清那几行不起眼的字符串拼接和编码转换——它们藏在层层嵌套的工具类里,且没有日志输出。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!










