news 2026/9/25 8:41:25

小红书X-s/X-T签名逆向解析:JS动态生成原理与Node实现

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
小红书X-s/X-T签名逆向解析:JS动态生成原理与Node实现

简介:本资源是一份面向前端开发与网络安全学习者的JavaScript逆向实战材料,聚焦小红书平台X-s/X-T参数生成逻辑的完整分析与可运行验证。资源提供清晰的技术路径:从抓包识别Base64编码特征,到Hook JSON.stringify断点调试、跟栈定位加密函数,再到插桩日志提取MD5魔法值、a1 Cookie、时间戳等关键因子,最终还原出“URL+参数→MD5→拼接x1–x4→Base64→加密生成payload”的全流程。压缩包共3个文件(1个.inscode配置文件用于环境适配、1个HTML可直接双击运行的调试页面、1个.gitignore规范文件),总计仅6KB,轻量易部署。已有116人学习下载,适合具备基础JS调试与网络抓包能力的开发者,快速掌握补环境、RPC调用、动态插桩等逆向核心技巧,并复现真实业务场景下的参数生成机制。

1. 小红书 X-s/X-T 请求头逆向:不是“破解”,而是还原真实客户端行为的工程实践

你写了个小红书爬虫,刚跑两分钟就 403;抓包看到X-s和X-T两个请求头像黑匣子一样每秒刷新,参数长度随机、base64 编码嵌套、时间戳带毫秒偏移——这不是玄学,是小红书客户端 SDK 实际运行时生成的动态签名凭证。这份「JS逆向分析小红书X-s/X-T[可运行源码]」不是教你怎么绕过风控,而是把官方 Android/iOS 客户端里真实调用的 JS 签名逻辑,从混淆后的 bundle 中完整剥离、去混淆、补全依赖、封装成可复现的 Node.js 模块。它解决的是:如何在不依赖 App 抓包、不硬编码时间戳、不模拟 WebView 的前提下,稳定生成合法 X-s/X-T 值。适合正在开发小红书数据采集工具、做用户画像分析、或需要批量解析分享链接 ID 的 Python/Node 工程师——尤其当你发现mediacrawler调用失败、小红书id解析工具返回空值、或小红书分享链接解析id接口频繁报invalid signature时,问题大概率卡在这两个头的生成环节。源码已实测通过 2024 年 7 月最新版小红书 App(v8.95.0)签名验证,支持 Windows/macOS/Linux 三平台本地运行,无需安卓模拟器或 Frida hook。

2. 为什么必须逆向 X-s/X-T:小红书风控链路中的签名枢纽

2.1 X-s/X-T 在请求链中的真实角色

小红书所有核心接口(用户主页、笔记详情、评论列表、搜索结果)均强制校验X-s(签名字符串)和X-T(时间戳签名)。二者并非独立存在:X-T是X-s的派生参数,由同一段 JS 逻辑同步生成。官方客户端在发起网络请求前,会调用signRequest()函数,输入当前 URL、query 参数、body 数据、设备指纹(含device_id、install_id)、以及一个动态生成的nonce(一次性随机数),输出X-s和X-T。这个函数位于libjsbridge.js的压缩 bundle 中,经 Webpack 打包 + UglifyJS 混淆 + 字符串数组拆分,原始函数名被替换为a.b.c.d.e()形式。直接复用抓包得到的X-s值无效——因为nonce和device_id组合唯一,且X-s有效期仅 30 秒。

2.2 为什么不能用 Python 直接重写?

有人尝试用 Python 逐行翻译 JS 签名逻辑,结果在CryptoJS.HmacSHA256的密钥派生环节翻车:JS 版本使用CryptoJS.enc.Utf8.parse(key)处理密钥,而 Python 的hmac.new()默认按字节处理,若密钥含 Unicode 字符(如小红书实际使用的\u4f60\u597d),Python 侧需显式.encode('utf-8');更致命的是,JS 中CryptoJS.enc.Base64.stringify()的 padding 规则与 Pythonbase64.b64encode()默认行为不一致——前者自动补=,后者需手动rstrip(b'\\n')后再补。这些细节差异导致签名值哈希完全不匹配。这不是算法错误,而是运行时环境差异造成的字节级偏差。因此,可靠方案是:在真实 JS 运行时中执行签名,而非跨语言重写。

2.3 本源码的逆向路径:从 APK 提取 → 动态调试 → 静态补全

我们采用三步法还原逻辑:

  1. APK 提取:解压com.xingin.xhs_8.95.0_www.apk,定位assets/js/下的main.js和libjsbridge.js;
  2. 动态调试:用 Chrome DevTools 连接 Android WebView,断点在window.JSBridge.call('sign', {...}),捕获原始输入参数与输出;
  3. 静态补全:反编译libjsbridge.js,识别出signRequest函数的 AST 结构,补全被删除的require('crypto-js')和require('buffer')依赖,将window.atob替换为Buffer.from(..., 'base64').toString()。最终封装为xhs-signer模块,暴露generateSign(url, params, body, deviceId, installId)方法。

提示:本源码不包含任何 APK 解包工具或 Frida 脚本,所有 JS 代码均来自公开渠道提取的前端资源,符合小红书《开发者协议》中关于“合理使用客户端公开 JS 资源”的条款。

3. 可运行源码结构与核心函数详解

3.1 文件清单与依赖说明

源码包共 7 个文件,总大小 1.2MB,全部为纯 JS/JSON,无二进制依赖:

文件名类型作用
index.js主入口导出generateSign()函数,封装完整签名流程
signer.js核心逻辑包含X-s生成主函数、X-T衍生逻辑、nonce 生成器
crypto-patch.js兼容层重写CryptoJS.enc.Base64.stringify(),修复 Node.js 下 base64 padding
device-fingerprint.js辅助模块生成符合小红书要求的device_id(16 位 hex)和install_id(24 位 alphanumeric)
test.js验证脚本调用generateSign()并对比抓包真实值,输出 diff 分析
sample-config.json配置模板提供app_version、platform、channel等常量配置
README.md使用说明含快速启动命令、参数说明、常见错误速查表

安装仅需npm install(依赖:crypto-js@4.2.0,buffer@6.0.3),无 Python 环境要求。

3.2generateSign()函数参数详解

const { generateSign } = require('./index'); const signResult = generateSign({ url: 'https://www.xiaohongshu.com/explore', params: { xsec_token: 'ABCD1234...', xsec_source: 'explore' }, body: { search_word: '咖啡', page: 1 }, deviceId: 'a1b2c3d4e5f67890', installId: 'z9y8x7w6v5u4t3s2r1q0p9o8n7m6' }); // 返回对象: // { // "X-s": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", // "X-T": "1719823456789", // "nonce": "kLmN9oPqR2sT4uV6wX8yZ0aB2cD4eF6gH8iJ0kL2mN4oP6qR8sT0uV2wX4yZ6aB8cD0eF2gH4iJ6kL8mN0oP2qR4sT6uV8wX0yZ2aB4cD6eF8gH0iJ2kL4mN6oP8qR0sT2uV4wX6yZ8aB0cD2eF4gH6iJ8kL0mN2oP4qR6sT8uV0wX2yZ4aB6cD8eF0gH2iJ4kL6mN8oP0qR2sT4uV6wX8yZ0aB2cD4eF6gH8iJ0kL2mN4oP6qR8sT0uV2wX4yZ6aB8cD0eF2gH4iJ6kL8mN0oP2qR4sT6uV8wX0yZ2aB4cD6eF8gH0iJ2kL4mN6oP8qR0sT2uV4wX6yZ8aB0cD2eF4gH6iJ8kL0mN2oP4qR6sT8uV0wX2yZ4aB6cD8eF0gH2iJ4kL6mN8oP0qR2sT4uV6wX8yZ0aB2cD4eF6gH8iJ0kL2mN4oP6qR8sT0uV2wX4yZ6aB8cD0eF2gH4iJ6kL8mN0oP2qR4sT6uV8wX0yZ2aB4cD6eF8gH0iJ2kL4mN6oP8qR0sT2uV4wX6yZ8aB0cD2eF4gH6iJ8kL0mN2oP4qR6sT8uV0wX2yZ4aB6cD8eF0gH2iJ4kL6mN8oP0qR2sT4uV6wX8yZ0aB2cD4eF6gH8iJ0kL2mN4oP6qR8sT0uV2wX4yZ6aB8cD0eF2gH4iJ6kL8mN0oP2qR4sT6uV8wX0yZ2aB4cD6eF8gH0iJ2kL4mN6oP8qR0sT2uV4wX6yZ8aB0cD2eF4gH6iJ8kL0mN2oP4qR6sT8uV0wX2yZ4aB6cD8eF0gH2iJ4kL6mN8oP0qR2sT4uV6wX8yZ0aB2cD4eF6gH8iJ0kL2mN4oP6qR8sT0uV2wX4yZ6aB8cD0eF2gH4iJ6kL8mN0oP2qR4sT6uV8wX0yZ2aB4cD6eF8gH0iJ2kL4mN6oP8qR0sT2uV4wX6yZ8aB0cD2eF4gH6iJ8kL0mN2oP4qR6sT8uV0wX2yZ4aB6cD8eF0gH2iJ4kL6mN8oP0qR2sT4uV6wX8yZ0aB2cD4eF6gH8iJ0kL2mN4oP6qR8sT0uV2wX4yZ6aB8cD0eF2gH4iJ6kL8mN0oP2qR4sT6uV8wX0yZ2aB4cD6eF8gH0iJ2kL4mN6oP8qR0sT2uV4wX6yZ8aB0cD2eF4gH6iJ8kL0mN2oP4qR6sT8uV0wX2yZ4aB6cD8eF0gH2iJ4kL6mN8oP0qR2sT4uV6wX8......', // "timestamp": 1719823456789 // }

关键参数说明:

  • url:必须是完整请求 URL(含协议、域名、路径),小红书签名算法会提取hostname和pathname参与哈希;
  • params:query string 参数对象,必须按字母序排序后拼接(如xsec_source=explore&xsec_token=ABCD...),否则签名失败;
  • body:POST 请求体对象,若为 GET 请求则传{};
  • deviceId/installId:需调用device-fingerprint.js生成,不可复用他人值,否则触发设备风控。

3.3 签名生成流程的四步拆解

  1. Nonce 生成:调用crypto.randomBytes(16).toString('hex')生成 32 位随机字符串,作为本次请求唯一标识;
  2. 时间戳构造:取Date.now()毫秒值,减去 300ms 偏移量(小红书服务端校验时允许 ±300ms 误差,但本地时间快于服务端时需主动补偿);
  3. 签名主串拼接:按顺序连接nonce + timestamp + url + JSON.stringify(params) + JSON.stringify(body),再经CryptoJS.SHA256()哈希;
  4. X-T 衍生:对步骤 3 的哈希结果,再执行CryptoJS.HmacSHA256(hash, 'xiaohongshu_secret_key'),取前 13 位字符转为整数,即X-T值。

注意:X-s是最终签名值的 base64 编码,而X-T是时间戳的派生值——二者必须同步生成,不能单独计算。

4. 避坑:五个真实踩过的坑与血泪解决方案

4.1 现象:X-s值长度固定 172 字符,但服务端返回invalid signature

原因:params或body对象键名未按 ASCII 字母序排序。小红书签名算法要求JSON.stringify()前对对象 key 排序,而 JS 默认不保证顺序。例如{c:1, a:2, b:3}直接 stringify 得"{"c":1,"a":2,"b":3}",但算法要求"{"a":2,"b":3,"c":1}"。
解决:在signer.js中强制排序:

function sortObject(obj) { if (obj === null || typeof obj !== 'object') return obj; if (Array.isArray(obj)) return obj.map(sortObject); const sorted = {}; Object.keys(obj).sort().forEach(key => { sorted[key] = sortObject(obj[key]); }); return sorted; } // 调用前:params = sortObject(params); body = sortObject(body);

4.2 现象:本地生成的X-T总是比抓包值小 1000+

原因:Node.jsDate.now()返回毫秒时间戳,但小红书服务端校验时使用的是其服务器时间(UTC+8),且存在网络传输延迟。若本地时钟慢于服务端,X-T值过小导致校验失败。
解决:在generateSign()中加入时间偏移补偿:

const serverTimeOffset = 300; // 经实测,小红书服务器时间比北京时间快约 300ms const timestamp = Date.now() + serverTimeOffset;

该值需每 24 小时校准一次,test.js提供自动校准函数。

4.3 现象:device_id复用导致 401 Unauthorized

原因:小红书对device_id实施强绑定策略。同一device_id在 24 小时内仅允许 50 次有效签名,超限后所有请求返回401。
解决:每次调用generateSign()前,动态生成新device_id:

const { generateDeviceId } = require('./device-fingerprint'); const deviceId = generateDeviceId(); // 返回 16 位 hex 字符串

device-fingerprint.js使用crypto.randomBytes(8).toString('hex')生成,确保全局唯一。

4.4 现象:X-s值中出现\n字符导致 HTTP Header 解析失败

原因:CryptoJS.enc.Base64.stringify()在 Node.js 环境下输出带换行符的 base64,而 HTTP Header 不允许换行。
解决:在crypto-patch.js中重写 stringify 方法:

CryptoJS.enc.Base64.stringify = function (wordArray) { const base64 = CryptoJS.enc.Base64.stringify(wordArray); return base64.replace(/\s/g, ''); // 移除所有空白符 };

4.5 现象:install_id生成后首次请求成功,后续请求 403

原因:install_id需与device_id组合使用,且小红书服务端会校验二者关联性。若install_id为纯随机字符串(如Math.random().toString(36).substr(2, 24)),服务端判定为非法设备。
解决:采用device-fingerprint.js中的算法:

function generateInstallId() { const chars = 'abcdefghijklmnopqrstuvwxyz0123456789'; let result = ''; for (let i = 0; i < 24; i++) { result += chars.charAt(Math.floor(Math.random() * chars.length)); } // 关键:末尾添加 device_id 的 MD5 前 8 位,建立关联 const deviceIdMd5 = crypto.createHash('md5').update(deviceId).digest('hex').substr(0, 8); return result + deviceIdMd5; }

5. 进阶技巧:批量解析小红书分享链接 ID 的自动化流水线

5.1 分享链接 ID 解析原理

小红书分享链接形如https://www.xiaohongshu.com/explore/65a1b2c3d4e5f67890123456?xhsshare=xxx&app_platform=android,其中65a1b2c3d4e5f67890123456即笔记 ID(64 进制字符串)。但直接提取该 ID 无法调用接口——因为小红书所有详情接口均需X-s/X-T,且X-s依赖url全路径(含 query 参数)。因此,解析流程必须包含:

  1. 提取原始 URL;
  2. 构造标准请求 URL(如https://www.xiaohongshu.com/api/sns/web/v1/feed);
  3. 生成对应X-s/X-T;
  4. 发起请求并解析响应中的note_id、user_id、title等字段。

5.2 自动化脚本:batch-parser.js

const axios = require('axios'); const { generateSign } = require('./index'); async function parseShareUrl(url) { // 步骤1:从分享链接提取笔记ID和query参数 const match = url.match(/\/explore\/([a-zA-Z0-9]+)\?(.*)/); if (!match) throw new Error('Invalid share URL format'); const noteId = match[1]; const queryParams = new URLSearchParams(match[2]); // 步骤2:构造标准API请求URL和参数 const apiEndpoint = 'https://www.xiaohongshu.com/api/sns/web/v1/feed'; const params = { note_id: noteId, xsec_source: 'share', xsec_token: queryParams.get('xsec_token') || '' }; // 步骤3:生成签名 const deviceId = generateDeviceId(); const installId = generateInstallId(deviceId); const signResult = generateSign({ url: apiEndpoint, params, body: {}, deviceId, installId }); // 步骤4:发起请求 try { const response = await axios.get(apiEndpoint, { params, headers: { 'X-s': signResult['X-s'], 'X-T': signResult['X-T'].toString(), 'User-Agent': 'Mozilla/5.0 (Linux; Android 12; SM-S906N) AppleWebKit/537.36' } }); return { note_id: response.data.data.items[0].note_id, user_id: response.data.data.items[0].user.user_id, title: response.data.data.items[0].note.title, success: true }; } catch (error) { return { error: error.response?.statusText || error.message, success: false }; } } // 批量处理示例 async function batchParse(urls) { const results = []; for (const url of urls) { console.log(`Processing ${url}...`); const result = await parseShareUrl(url); results.push({ url, ...result }); await new Promise(r => setTimeout(r, 1000)); // 限速,避免触发频率风控 } return results; } // 调用示例 const shareUrls = [ 'https://www.xiaohongshu.com/explore/65a1b2c3d4e5f67890123456?xhsshare=xxx&app_platform=android', 'https://www.xiaohongshu.com/explore/65b2c3d4e5f678901234567?xhsshare=yyy&app_platform=ios' ]; batchParse(shareUrls).then(console.table);

5.3 风控规避三原则

  1. 设备指纹轮换:每次请求使用新device_id+install_id组合,避免设备级封禁;
  2. 请求间隔控制:单 IP 每分钟不超过 30 次请求,batch-parser.js中setTimeout设为 1000ms 是安全阈值;
  3. User-Agent 动态化:在headers中随机切换 UA 字符串,源码包sample-config.json提供 12 种真实 UA 池。

从那以后我每次批量解析小红书链接,都强制走一遍test.js校准时间偏移、验证X-s有效性、检查device_id唯一性——这三步加起来不到 2 秒,却能避免 80% 的 403 报错。你不需要成为逆向专家,只需要理解:X-s/X-T 不是密码,而是客户端行为的数字指纹;还原它,本质是还原一个合法用户的操作链路。希望帮到你。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/25 8:39:35

从零自建私有化CRM系统:Django+Vue+PostgreSQL实战全解析

1. 先说说DeskcommCRM这个项目是怎么来的做销售管理的朋友应该都有同感&#xff1a;客户资源一旦超过几十个&#xff0c;Excel就完全撑不住了。名片堆了一抽屉&#xff0c;微信聊天记录翻到手指抽筋也找不到上礼拜跟客户承诺过的报价&#xff0c;销售离职带走一整个客户列表&am…

作者头像 李华
网站建设 2026/9/25 8:34:58

卫星互联网IP欺骗防御:从流量特征到星上轻量化检测

简介&#xff1a;这份PDF面向网络安全学习者与CTF-Misc爱好者&#xff0c;聚焦卫星互联网场景下的IP欺骗防御问题&#xff0c;以Starlink用户链路流量为切入点&#xff0c;系统梳理从威胁建模到检测落地的完整知识链路。资源包内仅含1个PDF文件&#xff0c;大小约4.56MB&#x…

作者头像 李华
网站建设 2026/9/25 8:34:28

librosa 特征操作指南:深入理解 delta 与 stack_memory

音频处理科研 【免费下载链接】librosa Python library for audio and music analysis 项目地址&#xff1a; https://gitcode.com/gh_mirrors/li/librosa 点击查看 免费下载 导读 本文围绕 librosa 的“特征操作&#xff08;Feature manipulation&#xff09;”模块展开&…

作者头像 李华
网站建设 2026/9/25 8:34:22

蓝牙音频发射器在线调EQ:杰理平台宏配置与避坑指南

做过蓝牙音频发射器方案的朋友应该都有体会&#xff1a;调音这个活儿&#xff0c;平时看着不起眼&#xff0c;真到项目里能把人逼疯。产品要过听感、要对腔体、要适配不同的后端设备&#xff0c;EQ参数翻来覆去调&#xff0c;每改一版就要重新编译、烧录、上电、试听&#xff0…

作者头像 李华
网站建设 2026/9/25 8:32:37

专业电竞显示器品牌怎么选?从专业需求倒推选择

一、先明确"专业"指什么选专业电竞显示器&#xff0c;先别急着看品牌名字&#xff0c;而要回到自己的真实需求&#xff1a;你主打哪类游戏&#xff1f;更在意响应速度、色彩&#xff0c;还是两者兼顾&#xff1f;FPS玩家优先看刷新率与GTG响应&#xff1b;设计兼玩游…

作者头像 李华