这不是一套单纯的“参数签名算法”,而是一条把业务参数、设备指纹、时间戳、随机扰动和多层加密混合在一起的 Token 生成链路。
在很多移动端或接口安全场景里,Token 并不是简单地由几个参数做一次 HMAC 就结束了。
有些方案会把设备信息、时间戳、随机数、自定义编码逻辑、标准密码学算法和非标准扰动算法混合在一起,构造出一条复杂的签名与加密链路。
本文分析的就是这样一套算法。
它的特点很明显:
从工程视角看,这套算法的复杂度明显高于常见的“参数排序 + HMAC”模式。
从逆向和迁移视角看,它真正难的地方,往往也不是 AES 或 HMAC,而是那些“看起来像标准算法、实际上已经被改造过”的细节,加上ollvm 和vmp 更让人想原地飞升。
如有侵权请联系我下架
如果只看结果,这套算法的目标很简单:生成一个最终的 Token 字符串。
但在内部,它实际上分成了四大部分:
最终 Token 的结构大致如下:
整套算法的输入主要可以分成两类。
最核心的业务参数通常是登录信息,例如:
这部分主要参与签名计算。
它决定的是:当前这个 Token 是为谁生成的、用于什么业务请求。
另一部分是设备相关信息,例如:
包名
这些参数不会简单明文拼接,而是先被编码成一个二进制块。
这个二进制块会同时进入:
也就是说,设备信息不是“装饰字段”,而是这套算法的核心组成部分。
这一层可以理解成“设备指纹封装层”。
它不是标准 JSON、XML,也不是普通的 URL 编码,而是一套自定义二进制封装逻辑。
编码流程大致如下
所有设备参数先按 key 排序,保证顺序稳定。
每个 key 在写入时,会额外叠加一个随机高字节。
这意味着即使 key 本身固定,最终写入值也带有一定随机性。
每个 value 的字符不是直接写入,而是逐字节做 nibble 级别变换,再写入 value 区。
编码后的 key 和 value 会被分别拼接,形成主体数据。
在主体前面还会加上一段固定头、随机字节,以及一些固定零填充区。
然后从固定偏移位置开始,对后续内容执行一次自定义 Murmur Hash。
最后把算出的 Hash 写回到固定位置。
这样就得到一个最终的设备参数二进制块。
这个数据块既是后续签名的一部分,也是设备密文生成的原始输入。
签名部分是这套方案最容易误判的地方。
表面上看它包含 HMAC,但实际上 HMAC 只是其中一个环节。
完整流程大致如下。
首先构造一个 signatureVector。
它不是单一业务字符串,而是把下面几类信息混合起来:
可以近似理解为:
这里的时间戳也不是直接十进制字符串,而是经过:
之后才参与拼接。
接下来会对 signatureVector 做一次 SHA 运算。
但这里的关键在于:它不是标准 SHA-256。
这一步被改造过的地方包括:
这意味着,哪怕算法名字看起来像 SHA-256,也不能直接替换成标准库。
这一步输出的是 32 字节摘要。
摘要不会整体参与下一步,而是:
也就是只使用中间的一部分内容作为后续输入。
这种“摘要后再截取”的做法,会进一步增加复现时的细节敏感度。
将上一步得到的截断字符串作为输入,再执行一次标准 HMAC-SHA256。
这一阶段使用固定 HMAC Key。
也就是说,标准密码学组件确实存在,但它是建立在一套自定义摘要处理之后的。
HMAC 结果转成大写十六进制后,再在前面拼接固定前缀:
然后只取前固定长度字节,形成新的待处理向量。
这一段待处理向量不会直接输出,而是会进入一套自定义混淆流程。
其思路是:
这一层主要由以下操作构成:
最终得到签名密文段。
除了签名之外,设备参数编码块本身还会被单独加密,生成最终 Token 中的另一大段内容。
可以把它理解成:设备密文段生成链路。
原始设备参数块首先进入 zlib 压缩。
这里的目的不仅是缩短长度,更重要的是打散原始结构,让明文特征不再直接可见。
压缩完成后,不会立刻进入 AES,而是先做一轮链式异或变换。
这一过程的特点是:
因此它比普通异或更复杂,也更难从局部观察规律。
这一步非常有特点。
算法会读取某个尾字节的低 4 位,并据此决定进入哪条分支。
不同分支中可能出现的操作包括:
换句话说,这里不是一条固定算法,而是一套多态扰动机制。
完成多态扰动后,数据会先做 16 字节对齐,然后执行第一层 AES:
使用固定 Key 和固定 IV。
第一层 AES 结束后,并不会直接输出,也不会立刻 Base64。
而是会遍历每个字节,对最低位做一次翻转处理。
这一步并不是标准密码学流程,而是额外插入的一层位级扰动。
之后再执行第二层 AES:
这一层使用另一组固定 Key 和 IV。
所以设备数据实际上经历了两层 AES,而且中间还夹着一层非标准位翻转处理。
第二层 AES 后,算法还会再做一次逐字节最低位翻转。
这个细节很容易在迁移时被忽略,但只要漏掉一次,最终密文就会完全不一致。
经过上述全部步骤后,结果再做 Base64 编码,得到最终的设备密文段。
到这里,算法已经得到两段最重要的数据:
再配合时间戳片段和固定头尾,就得到最终 Token。
结构如下:
固定前缀
用于标记这类 Token 的版本或类型。
时间戳片段
提供时效性信息,也间接参与签名一致性。
select
来自签名处理中间过程的提取值,可以看作一个小型控制标记。
签名密文段
负责将业务参数、设备参数和时间戳绑定在一起。
设备密文段
负责封装和隐藏设备指纹信息。
固定后缀
作为格式尾标识。
从设计角度看,这套算法有几个非常明显的特点。
常见接口签名可能只是:
而这里实际包含:
这是一种明显的“多层混合式方案”。
设备参数不仅参与最终密文,而且直接参与签名向量构造。
这意味着:
同样的业务参数,在不同设备参数下会得到不同 Token。
因此它天然带有较强的设备绑定特征。
时间戳并不是简单地附加在末尾,而是深入参与了:
所以它属于典型的时效型 Token,不适合长期复用。
参数编码过程中存在随机内容,例如:
key 的高字节扰动
固定头之后的随机字节
这说明即使业务输入一致,输出也不一定逐次完全相同。
这类算法最难处理的,不一定是完全陌生的自定义逻辑,而是那些“长得像标准实现”的部分。
例如:
这类实现最容易让人在迁移时掉坑。
如果把它实现成 Java 项目,比较合理的结构一般是:
参数对象层
统一管理:
等业务和设备字段。
参数编码层
负责把设备参数组装成原始二进制块,并计算回填自定义 Hash。
Hash 层
负责:
自定义 Murmur Hash
自定义 SHA
加密工具层
负责:
总入口服务层
统一暴露一个总方法,例如:
由这个入口把全部步骤串起来,直接返回最终 Token。
如果用一句话概括,它就是:
将业务参数和设备参数先编码成二进制块,再结合时间戳构造签名向量,通过自定义 SHA 与 HMAC 生成签名密文,同时对设备数据进行压缩、多态扰动和双阶段 AES 加密,最后拼接成一个具备设备绑定和时效特征的 Token。
这类算法真正难的地方,从来不只是“用了什么密码学算法”,而是:
参数如何编码
在迁移、重写、逆向分析或跨语言复现时,真正决定结果能否一致的,通常就是这些最容易被忽略的细节。
也正因为如此,这类 Token 方案往往看上去只是“签名 + 加密”,真正拆开后才会发现:
它其实是一套经过多层封装和扰动的复合算法链路。
测试通过性




000x_ + 时间戳片段 + select + 签名密文段 + 设备密文段 + _xxxxx_
000x_ + 时间戳片段 + select + 签名密文段 + 设备密文段 + _xxxxx_
{"loginId":"xxxxxxxx"}
javaDataString + "&0xxxx_" + kvPair + "&" + timestampHex
javaDataString + "&0xxxx_" + kvPair + "&" + timestampHex
xxxxxx + HMAC_HEX
AES/CBC/NoPadding
AES/CFB/NoPadding
000x_
+ 时间戳片段
+ select
+ 签名密文段
+ 设备密文段
+ _xxxx_
000x_
+ 时间戳片段
+ select
+ 签名密文段
+ 设备密文段
+ _xxxx_
XXXAPI.XXXX(payload)
这不是一套单纯的“参数签名算法”,而是一条把业务参数、设备指纹、时间戳、随机扰动和多层加密混合在一起的 Token 生成链路。
传播安全知识、拓宽行业人脉——看雪讲师团队等你加入!
最后于 22小时前
被一只笨猫编辑
,原因: