
简介面向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这种把环境检测、动态密钥、算法混淆结合在一起的设计任何一步靠猜都走不远必须有一套系统的分析流程兜底。如果你现在也卡在某个前端加密算法的分析上我的建议是先把入口点找到然后把输入输出用日志完整记录下来再去做对比分析效率和成功率都会高很多。希望这篇文章里的思路能给你打开一条路。本文还有配套的精品资源点击获取