如何在Python爬虫中模拟App的Header签名算法_使用PyCryptodome库重现加密逻辑

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

为什么直接复制App的Header签名会失效

App端签名通常不是固定字符串,而是基于时间戳、随机数、请求体、密钥等动态参数实时计算的HMAC-SHA256或AES-CBC结果。抓包看到的X-SignSignature字段每次都不一样,硬编码必然失败。

关键点在于:签名逻辑往往藏在so库或混淆后的Java/Kotlin代码里,但多数情况下,核心加密流程仍依赖标准算法——PyCryptodome能覆盖绝大多数场景,前提是还原出正确的输入拼接方式和密钥处理逻辑。

  • 常见错误现象:Signature verification failed401 Unauthorized,即使时间戳、nonce、body都对得上
  • 根本原因:密钥被二次处理(如base64解码后再取前16字节)、原始数据未按约定排序/拼接/编码(比如要求UTF-8 bytes但用了str)、缺少预置IV或padding方式不匹配
  • 调试建议:用Frida Hook住签名函数,打印出实际传入的keydataiv三元组,这是最可靠的输入依据

用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

假设逆向得到原始密钥为 base64 字符串,且需解码

raw_key = "dGhpcyBpcyBhIHNlY3JldA==" key = base64.b64decode(raw_key)

拼接规则示例:method + \n + path + \n + timestamp + \n + nonce + \n + body_md5

message = f"POST\n/api/v1/user\n1717023456\nabc123\n{md5(body.encode()).hexdigest()}".encode()

如何在Python爬虫中模拟App的Header签名算法_使用PyCryptodome库重现加密逻辑
Python 3.14.3

微软官方的 Python 扩展,是 VS Code 安装量最高的扩展(209M+)。集成 IntelliSense(通过 Pylance)、调试(通过 Python Debugger)、代码检查、格式化、重构和单元测试等功能。支持 Jupyter Notebook、虚拟环境管理和多 Python 版本切换。

下载

signature = HMAC(key, message, sha256).hexdigest() headers["X-Sign"] = signature

  • 务必检查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

key = b"your-32-byte-key-here-123456789012" iv = b"0102030405060708" # 必须16字节 data = "timestamp=1717023456&nonce=abc123&body={}".format(body).encode()

cipher = AES.new(key, AES.MODE_CBC, iv) encrypted = cipher.encrypt(pad(data, AES.block_size)) signature = encrypted.hex() headers["X-Sign"] = signature

  • 如果服务端返回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()前的inputkey
  • 对比Python与App端的message十六进制dump(message.hex()),一个换行符、一个空格、一个URL编码差异都会导致签名不同
  • 服务端有时会校验User-AgentX-App-Version,这些虽不参与签名,但缺失会导致整个请求被拦截,误判为签名错误

逆向签名逻辑最难的从来不是算法本身,而是搞清那几行不起眼的字符串拼接和编码转换——它们藏在层层嵌套的工具类里,且没有日志输出。

相关推荐:

如何解决Python爬虫请求频繁被重定向的问题_设置allow_redirects参数

requests.get() 默认自动跳转(allow_redirects=True)会导致爬虫失效,因反爬系统常利用301/302跳转至验证码页或空页,使你无法获取原始响应头和状态码,进而中断后续逻辑。 为什么 requests.get() 默认会自动跳转,反而导致爬虫失效 因为 requests 默认开启重定向追踪(allow_redirects=True),遇到 301、302 响应时会自动...

如何解决Python爬虫在长连接请求中超时的问题_设置合理的timeout参数与重试机制

requests.get()默认不设超时会无限等待,必须用timeout=(connect, read)元组分别控制连接与读取超时,单数值仅作用于读取阶段,连接阶段仍可能卡死。 requests.get() 默认不设超时,长连接请求卡住几小时都可能不报错——必须显式设置 timeout,且不能只填一个数字。 为什么单个 timeout 数值对长连接无效 requests 的 timeout 参数若...

如何在Python中检测单链表中是否存在环_使用快慢指针(Floyd判圈算法)

快慢指针能检测环是因为有环时相对速度为1,有限步内必相遇;无环时快指针先达None。需检查空链表、单节点、自环等边界条件,循环条件应为while fast and fast.next,用is比较节点,相遇后可求环入口。 快慢指针为什么能检测环 核心在于:如果链表有环,快指针(每次走2步)迟早会追上慢指针(每次走1步);如果无环,快指针必然先走到 None。这不是概率问题,而是数学上的相遇必然性——...

如何在Python Django中实现分库分表逻辑_利用Router控制模型路由到指定库

Django的DatabaseRouter仅支持分库,无法自动分表,因其只在查询前选择数据库别名,不干预SQL生成与表名替换,分表需手动处理建表、查询、写入及迁移。 分库分表不能靠 Django 的 DatabaseRouter 完全实现,它只负责“路由到库”,不处理“表拆分逻辑”;真正要分表(比如按时间/用户ID哈希),必须手动干预查询、写入和迁移流程。 为什么 DatabaseRouter 只...

如何修复循环仅执行一次的矩阵零值检测逻辑

本文解析了因函数逻辑错误导致 while 循环提前终止的根本原因,并提供使用 any() 优化零值检测的正确实现方案,确保矩阵中所有零元素被彻底处理。 本文解析了因函数逻辑错误导致 while 循环提前终止的根本原因,并提供使用 `any()` 优化零值检测的正确实现方案,确保矩阵中所有零元素被彻底处理。 问题核心在于 not_solved() 函数的逻辑缺陷:它并未真正判断“矩阵中是否存在零”,...

Python如何实现一致性哈希算法_解决分布式缓存数据分配不均

一致性哈希通过固定哈希空间(如2^32)构建环形结构,节点和数据映射其上并顺时针就近分配;增删节点仅影响邻近区间,大幅减少数据迁移;引入虚拟节点可缓解物理节点分布不均问题,提升负载均衡性。 直接用 hash_ring 库最省事,但真要自己写、调参或排查分布不均问题,必须理解虚拟节点数量、哈希函数输出范围、环结构更新时机这三个关键点。 为什么简单取模(hash(key) % N)在节点变动时会崩 节...

如何在Python中实现逻辑回归的特征重要性评估_分析coef_属性权重

逻辑回归的 coef_ 不能直接当特征重要性用,因其受量纲、标准化、正则化强度、class_weight 和样本权重等多重因素影响;需标准化+合理正则后才可粗略参考 abs(coef_),更推荐用 permutation_importance 在验证集上评估。 为什么 coef_ 不能直接当“特征重要性”用 逻辑回归的 coef_ 是线性决策边界的权重,它反映的是每个特征对 log-odds(对数...

Python中Scikit-learn 1.2版本如何处理缺失值_了解集成算法的原生支持

SimpleImputer是scikit-learn中处理缺失值的核心工具,自1.2版本起Imputer已移除,且strategy参数必须显式指定;支持mean、median(仅数值)、most_frequent(数值/类别)、constant(需fill_value)四种策略;不支持混合数据类型统一填充,须分列处理;fit_transform仅限训练集,测试集只能transform,否则导致评估...

Python如何优雅处理异步函数的超时逻辑_对比wait_for与asyncio.wait

asyncio.wait_for适合单任务超时,但会取消整个协程;asyncio.wait更灵活但timeout作用于整体等待过程;Python 3.11+推荐用async with asyncio.timeout()确保资源安全。 asyncio.wait_for 适合单任务超时,但会取消整个协程 当你只关心一个异步操作的耗时上限,asyncio.wait_for 是最直接的选择。它会在超时后主...