简介:面向Web安全与JavaScript逆向学习者,京东h5st 5.2.0加密分析项目源码以HTML页面为核心,聚焦第五段、第八段与第九段加密算法的生成逻辑,帮助读者从源码层面理解前端签名参数的构造过程。压缩包共3个文件,包含HTML演示页、inscode环境配置与gitignore辅助文件,整体仅6KB,结构紧凑,便于快速定位核心代码。已有154人学习使用,适合具备一定逆向基础、希望研究h5st签名机制的开发者。项目中对第八段加密过程通过base64编码、字母替换和倒转操作进行了清晰呈现,并展示了第九段与第五段共用同一加密入口但入参不同的设计差异;读者可对比分析参数变化对密文结果的影响,进而掌握JavaScript加密算法在实际业务中的调用方式。仅供技术学习交流,严禁用于商业或非法用途,使用时请遵守相关法律法规。 作为一个常年跟前端接口打交道的人,我拆过不少网站的签名算法,但京东这个h5st确实值得单独写一篇。尤其是5.2.0这个版本,它把前端加密的对抗思路提升了一个档次,已经不是单纯"找个加密函数复制一下"就能搞定的阶段了。这篇文章我会把整个分析过程、算法结构、定位思路和源码框架都摊开来讲,希望能给正在研究前端加密、接口安全或者想做技术复现的朋友一些实在的参考。
先说清楚前提:我这里聊的是技术学习和安全研究层面的逆向分析,目的是理解电商平台前端加密的设计思路、提升自己在接口调试和前端防护方面的能力,不是鼓励去恶意抓取数据或者绕过风控干违规的事。明白了这一点,下面的内容才有讨论的价值。
1. 项目概述与核心需求解析
1.1 h5st 5.2.0 是什么
h5st是京东在前端业务请求中广泛使用的一个动态签名参数,全称大致是"h5 signature"的意思。你打开京东的H5页面或者小程序WebView里的页面,随便触发几个接口,请求参数里基本都能看到这个字段。它本质上是前端把请求的关键信息(比如时间戳、请求参数、设备指纹等)做一系列算法处理后生成的一串签名,服务端收到请求后会校验这个签名的合法性,从而判断这次请求是不是从正常页面发出的。
5.2.0是h5st这个算法的一个版本编号。京东的h5st迭代过很多个版本,不同版本在算法结构、加密方式、环境检测强度上差异很大。5.2.0可以算是一个分水岭:它开始大量引入WebAssembly、浏览器指纹采集、JS运行时环境检测等技术,纯粹靠静态分析来看代码已经非常吃力了。我当时拆这个版本的时候,最大的感受就是它已经不是一个"纯JS加密",而是一个"浏览器环境与算法协同工作"的完整验证体系。
1.2 为什么值得去分析它
从纯技术角度看,h5st 5.2.0的设计思路非常典型。它涵盖了前端加密常见的三大难题:算法混淆、环境模拟、动态更新。你把这三个点吃透了,再去看市面上其他平台的签名算法(比如淘宝的sign、小红书的x-s等),会发现很多套路都是相通的。
再说实际应用层面,做爬虫或者接口调试的朋友应该都有体会:现在很多接口不是加个header就能过的,它要求你每次请求都要带着一个新鲜的、由前端JS实时计算出来的签名。你如果只是静态地把签名写死,很快就会被服务端识别并拒绝。这时候你就需要把整个签名算法用Python或者Node.js复现出来,在每次请求时动态生成签名,模拟浏览器的行为。
另外,h5st的分析过程本身也是一次很好的前端安全学习案例。你可以看到一个大厂是如何在前端代码里构建防御体系的:如何干扰调试器、如何检测非浏览器环境、如何对核心算法做加密和封装。这些东西对做前端安全、风控系统设计的朋友来说,是很好的参考资料。
我把这个项目拆完之后的产出物是一套完整的分析文档加源码工程,核心内容包括三个部分:算法流程的逆向还原、签名生成的代码实现、以及一套调试验证工具。下面逐个部分详细说。
2. h5st 5.2.0 算法结构拆解
2.1 整体参数构成
先看h5st这个参数本身的形态。正常情况下,你抓包看到一个h5st值会是类似这样的结构:
2.0_xxxxx_yyyyyy_zzzzzz_wwwwww这个串总共分好几段,每一段都有明确的含义。我用一个通用的标注来说明:
| 段位 | 含义 | 说明 |
|---|---|---|
| 第一段 | 算法版本号 | 标识当前的签名算法版本,5.2.0对应的可能是某种数字编码 |
| 第二段 | 时间戳信息 | 签名生成时的时间,用于服务端校验时效性,防重放攻击 |
| 第三段 | 随机数 | 每次请求生成的随机因子,保证同一个请求参数在不同时间生成的签名不同 |
| 第四段 | 签名摘要 | 核心签名结果,由业务参数、时间戳、指纹等信息计算得出 |
| 第五段 | 附加信息 | 指纹摘要或状态信息,用于服务端校验浏览器环境 |
这里要注意,不同版本的分段方式不一样,而且京东不同业务线使用的h5st规则可能也有细微差异。我在分析的时候是在一个通用商品详情页接口的上下文中定位到的。
2.2 核心算法流程
h5st 5.2.0的整体生成流程,我把它简化成下面几个关键步骤:
- 采集终端信息:通过JS读取浏览器指纹,包括UserAgent、屏幕分辨率、时区、Canvas指纹、WebGL信息等,然后对这些信息做摘要计算,得到一个环境指纹串。
- 生成时间戳和随机数:从性能和防重放角度,算法会要求时间戳在合理范围内,随机数保证每次签名的唯一性。
- 组织待签名参数:把业务请求中的关键参数(比如商品ID、页面ID等)和时间戳、随机数、指纹串按照某种顺序拼接成一个字符串,这个拼接规则是整个算法的关键点。
- 执行核心签名计算:对拼接后的字符串做加密摘要计算,这里5.2.0用到了自定义的HMAC变形算法,还会混入一个动态的密钥。
- 包装输出:把版本号、时间戳、随机数、签名结果、指纹串按一定规则编码拼接成最终的h5st字段。
这里的"动态密钥"是一个很重要的设计。它不是一个写死的常量,而是通过另一个随机数加一段固定的密钥种子计算出来的。也就是说,即使你完全复现了算法流程,拿到了同样的输入参数,如果动态密钥的计算方式不对,生成的签名依然校验不过去。
2.3 算法难度评估
我自己在拆解过程中的体感是,5.2.0比之前的4.x版本难度至少高了一个档次。主要体现在这几个方面:
- 代码混淆程度极高:所有函数名和变量名都是十六进制短字符串,控制流被扁平化处理,你基本上没办法通过阅读源码来理解它的逻辑。
- 环境检测前置化:算法会先检测执行环境(是不是浏览器、有没有完整的DOM、Canvas渲染是否正常),如果在Node.js里直接跑,很可能会走一个错误的执行分支,生成一个错误的签名。
- 动态密钥机制:密钥不是固定的,导致你没法通过单纯调用原函数的方式来复用签名生成逻辑。
所以说,分析这类算法,不能只靠"搜一个加密函数,把它抠出来"这种老办法,必须有系统化的思路。
3. 定位与调试:JS逆向分析全过程
3.1 第一步:定位加密入口点
拿到一个京东H5页面,第一步永远是定位h5st是在哪里生成的。方法有很多,我用的是最经典、也是最有效的一套组合拳。
先在Chrome开发者工具的Sources面板里,用全局搜索功能搜索"h5st"这个字符串。因为参数名最终是h5st,所以生成它的时候,代码里必然会有一个地方在给这个字段赋值。搜索结果会定位到一段JS代码,里面可能有类似这样的写法:
params.h5st = genSingature(...)或者:
var h5st = xx.getH5St(...)找到这个赋值点之后,在那一行打上断点,然后重新触发一次接口请求,代码就会在这个位置停下来。这时候你就拿到了h5st生成函数的调用栈——从哪个函数调用过来的、传了什么参数、返回值是什么,全都一清二楚。
这个步骤是整个分析的基石。很多新手一上来就去看混淆代码,结果看半天都不知道从哪看起。先找到入口,再顺着调用链往里钻,效率是最高的。
3.2 第二步:跟栈与Hook分析
打完断点之后,进入生成h5st的那个函数内部。这时候你会面临一个现实问题:函数内部逻辑非常复杂,而且函数体可能被混淆成几个大的while循环,单纯靠"人肉跟踪变量"基本行不通。
我的做法是双管齐下。一边是逐步执行,把每一步关键的中间结果记录下来,看下有没有明显的字符串拼接、加密调用、Base64编码等特征操作。另一边是采用Hook技术,在关键的函数入口处下钩子,把每次调用的输入输出全部打印出来。
比如,如果发现代码里调用了某个方法(比如一个疑似MD5摘要的函数),可以在控制台里临时用自定义函数替换掉原函数:
const originalFunc = targetObj.someMethod; targetObj.someMethod = function(...args) { console.log("调用参数:", args); const result = originalFunc.apply(this, args); console.log("返回结果:", result); return result; };通过这个办法,可以把算法内部各环节的输入输出全部记录下来,然后手工还原整个数据流。这个方法对5.2.0来说依然有效,只是效率相比静态分析高很多。
3.3 第三步:补环境与动态调试
当你把调用关系摸清了之后,下一步面临的问题就是:这些代码在Node.js环境里能不能跑起来?h5st 5.2.0里面的环境检测会检查window对象、document对象、Canvas、WebGL等等。在Node.js里,这些对象都不存在,代码就会报错或者走错的逻辑分支。
这时就需要做"补环境"了。思路很简单:在Node.js里手动创建window和document等全局对象的mock版本,把代码执行起来需要访问到的那些属性、方法全部补全。h5st 5.2.0的检测点非常多,有些属性夹在加密逻辑深处,不仔细跟一遍根本发现不了。我整理过一份环境检测点清单,大概有几十个检测点,包括:
- 浏览器UA信息与版本号一致性
- document下各种元素创建与样式计算
- Canvas指纹(对相同的文字做canvas渲染然后取像素数据做摘要)
- WebGL的渲染器名称
- 时区、语言等区域信息
补环境是一个拼耐心的活,但也是理解整个算法最好的过程。补好了之后,你会对浏览器指纹的概念有更深刻的理解——原来WebGL的渲染器名称、Canvas的像素数据这些看起来无害的信息,都可以拿来作为请求的合法性验证依据。
3.4 第四步:日志辅助分析
很多时候,动态调试一深入,断点太多反而理不清逻辑。我的经验是配合日志辅助分析。在不改变核心逻辑的前提下,在关键分支处插入console.log,输出当时走了哪个分支、关键数据是什么,然后再触发一次完整的请求流程,把日志全部导出来,逐步对照分析。
比如,我排查一个"为什么生成的签名总是校验不过"的问题时,就是通过日志对比发现,我在补环境时漏掉了一个UA解析步骤,导致指纹串里的浏览器版本和实际请求头里的版本不一致。这种问题如果不加日志,只看结果永远发现不了。
4. 代码实现与源码架构设计
整个分析完成之后,我搭建了一套独立的签名生成工程,用来在服务端环境(Node.js)中复现h5st的生成过程,从而支撑接口调试、自动化测试等场景。这个工程的整体设计是下面这个样子的。
4.1 整体代码框架
project/ ├── src/ │ ├── config/ # 配置文件,包含环境参数、固定密钥种子等 │ ├── core/ # 核心算法模块 │ │ ├── environment/ # 环境补全模块 │ │ ├── signature/ # 签名计算模块 │ │ └── utils/ # 摘要、编码、加解密工具 │ ├── entry.js # 统一的入口函数 │ └── index.js # 对外暴露的签名生成接口 └── test/ ├── unit/ # 单元测试,校验算法中间结果的正确性 └── e2e/ # 端到端测试,对比真实页面生成的签名在搭建这套框架时,我的思路是"模块与逻辑分离、功能与数据分离"。环境补全模块负责构造一个可运行的类浏览器环境;核心算法模块负责具体的加密计算;入口函数把这两个模块结合起来,输出最终的签名。这样一旦京东升级了算法逻辑(比如从5.2.0升到5.3.0),我只需要替换核心算法模块,其余部分可以复用。
4.2 环境补全模块实现要点
环境补全模块是最繁琐但也最关键的一环。它的目标很简单:让目标JS代码以为自己在浏览器里运行。
先说一个常见的坑:直接给globalThis挂一堆window属性,在某些情况下不够用,因为代码里可能还会创建iframe、操作DOM等。h5st 5.2.0对环境的检测,不仅仅是看对象存不存在,还会看对象内部的方法返回值是否符合真实浏览器的行为。比如Canvas指纹,它会在一个隐藏的canvas元素上绘制文字,然后读取像素数据做摘要。如果你只是mock了一个空壳canvas,返回的不是真实的像素数据,签名结果就会不对。
我当时实现了一个通用环境类,核心代码大致是这样:
const { Canvas } = require('canvas'); const util = require('util'); function createFakeWindow() { const fakeWindow = {}; fakeWindow.screen = { width: 1920, height: 1080, colorDepth: 24 }; fakeWindow.navigator = { userAgent: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36', language: 'zh-CN', platform: 'Win32', hardwareConcurrency: 8, deviceMemory: 8 }; fakeWindow.document = createFakeDocument(); fakeWindow.CanvasRenderingContext2D = function() {}; return fakeWindow; }这里需要说明的是,补环境代码看似简单,实际上必须配合真实的浏览器环境来校验。我的做法是:先在真实浏览器中生成一个签名,然后在Node.js中跑同一套逻辑,对比两边的指纹串是否一致。如果不一致,就说明环境补得有偏差,需要逐步校准。
4.3 签名计算模块实现
签名计算模块是核心中的核心。经过逆向分析,我把5.2.0的签名计算流程抽象成可复现的代码如下:
const crypto = require('crypto'); const { getFingerprint } = require('./fingerprint'); function generateH5ST(config) { const timestamp = Math.floor(Date.now() / 1000).toString(); const randomNum = generateRandomNum(); // 8位随机数字 const fingerprint = getFingerprint(); // 从环境补全模块获取 const signStr = buildSignString(config.params, timestamp, randomNum, fingerprint); const dynamicKey = calcDynamicKey(timestamp, randomNum); const sign = customHmac(signStr, dynamicKey); return `2.0_${timestamp}_${randomNum}_${sign}_${fingerprint}`; }这里的customHmac是我根据逆向结果还原出来的一个HMAC变体,它并不是标准的HMAC-SHA256,而是在标准算法基础上做了一些调整,比如对密钥做了额外的变换、对消息的填充方式做了改动。
buildSignString的拼接顺序也是一大要点。这个顺序不对,前面的工作全白做。我是通过反复调试、对比不同请求参数下签名的变化规律,才反推出拼接规则的。这也提醒大家,在分析这类签名算法时,一定要多做输入输出的对比实验,不要只看代码。
4.4 可复现与校验方案
为了保证我写的代码是"正确的",我设计了一套校验方案。在浏览器页面里,通过开发者工具手动修改某个环境参数(比如改一下屏幕分辨率),同时记录下对应的h5st变化情况;然后在Node.js里做同样的修改,看生成的h5st是否同步变化。如果两边变化规律一致,说明算法还原是正确的。
这个方法虽然比较笨,但胜在可靠。对于那些加了混淆、你没法100%看懂每一步具体逻辑的场景,用"行为对比"来验证总是最放心的。
5. 常见问题与排查技巧实录
整个项目做下来,我踩了不少坑。有些问题查了很久才找到原因,这里整理成一份速查表,希望能帮你少走弯路。
| 问题现象 | 可能原因 | 排查思路与解决办法 |
|---|---|---|
| 在Node.js中生成签名后,接口返回参数错误 | 环境补全不完整,指纹计算与真实浏览器不一致 | 对比浏览器环境与补全环境的指纹串差异,逐步校准 |
| 签名生成速度太慢,每次请求都要几百毫秒 | 环境初始化重复执行,引入了无谓的计算 | 将环境补全结果缓存为单例,只在进程启动时初始化一次 |
| 生成的签名偶尔能用、偶尔不能用 | 随机数或动态密钥的生成方式有误 | 在浏览器和Node.js中分别跑多次,统计随机数长度与分布规律 |
| 接口返回"请求过期" | 时间戳使用了秒级或毫秒级的差异导致校验超时 | 确认时间戳的单位和校验窗口,世界标准时间与本地时间要校准 |
| 代码一执行就进入错误的加密分支 | 环境检测点没有被完整模拟 | 在debugger模式下手动执行环境检测函数,看它具体访问了哪些属性 |
5.1 一个典型的排查案例
我记得有一次,我在Node.js里生成签名时,发现同一个时间戳和随机数下,每次生成的签名都不一样。这就很奇怪了,理论上相同的输入应该得到相同的输出。后来加了日志才发现,问题出在指纹串上——Canvas指纹生成的时候,由于Node.js的canvas库和浏览器的渲染结果存在细微差异,导致指纹每次都在变,进而影响了签名。
这个问题的解决方法是:把指纹串在进程启动时固定下来,模拟成一个"固定的浏览器环境快照"。虽然这个快照在真实浏览器中不会出现,但对于签名算法本身来说,它校验的是一个"合理且稳定的环境",固定指纹反而更符合它的预期。
5.2 关于代码更新的应对
还要提醒大家一点:京东的h5st算法是动态更新的,可能你刚分析完5.2.0,过一周它就升到5.2.1甚至5.3.0了。所以做这类分析,千万不要只盯着"写死的源码",要构建一套"可快速适配新版本"的框架。最好的方式是把你还原出来的算法逻辑整理成详细的文档,把每一个关键步骤标清楚、留痕,这样即使下次算法更新,你也可以快速定位到变化点,直接用文档对照着改代码。
5.3 合规提醒
最后,说句实在话。分析前端的加密算法,最大的价值在于学习安全设计和逆向思维,这些东西对做安全测试、接口调试、自动化巡检都有很大的帮助。但如果只是想着绕过风控去抓数据、做商业爬虫,那不仅技术走不远,还可能给自己惹上麻烦。我在做这个项目时,全程只在受控环境和测试接口上验证,这一点也建议你务必重视。
6. 写在最后的一点体会
拆h5st 5.2.0这个项目,前后花了我大概两周的业余时间。回看整个过程,最深的体会是:遇到高强度混淆的JS代码,不要想着硬碰硬地把每一行都读懂。先找入口、再抓数据流、最后用行为对比验证结果,这套方法论远比死磕代码本身更高效。尤其是像h5st这种把环境检测、动态密钥、算法混淆结合在一起的设计,任何一步靠猜都走不远,必须有一套系统的分析流程兜底。
如果你现在也卡在某个前端加密算法的分析上,我的建议是:先把入口点找到,然后把输入输出用日志完整记录下来,再去做对比分析,效率和成功率都会高很多。希望这篇文章里的思路能给你打开一条路。
本文还有配套的精品资源,点击获取