当前浏览的是 v7 预览版文档(v7 尚未正式发布,API 可能随版本调整)。本页是 v7 新增内容,去 v6 正式版文档首页
@ikenxuan/amagi

快手

$HE_ 段可整段拆开(时钟与计数器原样读回);HUDR 段拆不开,说了就是答案

快手签名验证

快手的 __NS_hxfalcon 签名由两段拼成:HUDR_<base64url 密文> + $HE_<hex>。两段的可验证面完全不同 —— $HE_ 段布局固定、可整段拆开;HUDR 段整段是 ChaCha 密文,底下没有明文可找。

布局常量 + 双层 LRC + 可还原时钟与计数器

签名输入

演示值由本实现对钉死输入产出 —— 仓库里还没有浏览器产出的快手签名捕获

判定

这是一份结构良好的 $HE_ 段 —— 两层 LRC 与全部布局常量都对上。

时钟签名封住的是 (2026-09-10T03:25:42Z)
计数器count = (异或掩码已解掉,随签名次数递增)
HUDR 段HUDR_ 前缀与字符集都对 —— 但它整段是 ChaCha 密文,内容拆不开
hash field签名里的 4 字节是 ;重算它需要 signInput 与 HUDR body (deriveKuaishouHeHashFieldHex),输入凑不齐就别猜

$HE_ 段:能拆到什么

最终输出是 45 字节 hex,最后一个字节是异或键 —— 它本身是前 44 字节的 LRC 校验,同时把那 44 字节逐个异或成密文。所以解码顺序固定为「拆键 → 还原 → 逐段校验 → 读字段」:

字段位置(还原后)能验 / 能读
header 魔数0–1恒为 4B54
版本块2–3恒为 cda9
启动标记4恒为 ab
startupRandom5–10原样读回(页面加载时的一次性随机数)
random11–16原样读回(本条签名自己的 48 位随机数)
固定体17–21恒为 0100000001
count22–25异或掩码已解掉,原样读回
hash field26–29读得出;重算需要 signInput + HUDR body
时钟30–35异或掩码已解掉,原样读回(毫秒)
尾段36–42恒为 9b563eda7b563e
两层 LRC43 / 44信封 LRC 覆盖全部 44 字节,尾段 LRC 只覆盖尾段

能同时通过两层校验与四个布局常量的值,必然是这套装配顺序产出的 —— 这就是「结构指纹」,与 a_bogus 的校验和同理。

hash field 为什么不自动重算

它绑的是 signInput + HUDR body:前者由请求 URL 与载荷推导,后者是随实例状态滚动的 密文。捕获签名时这两样通常不在手边 —— 输入凑不齐就别猜,凑齐了可以用 deriveKuaishouHeHashFieldHex 自行比对。

HUDR 段:拆不开,说了就是答案

HUDR 段的载荷经过掩码与 ChaCha 加密(密钥写死在前端代码里,amagi 与之对齐),内容对验证器而言是不可还原的。能验的只有外壳:HUDR_ 前缀与 base64url 字符集。这不是缺口 —— 它的作用就是让外部无法拼装签名,能拆开反而是坏事。

诚实的局限:没有外部 oracle

$HE_ 的结构断言全部来自本仓库实现与前端代码的对齐,仓库里没有一份浏览器产出的快手签名捕获(不像抖音的 a_bogus 有 oracle fixture)。所以「结构良好」证明的是装配顺序对齐,不能证明常量全部新鲜。快手前端若改了布局常量(header、版本块、尾段),验证器会开始报「对不上」—— 那时该重新对一遍前端代码。

签名状态随实例走

count 随每次签名递增、startupRandom 按实例生成、匿名 kww 按实例缓存 —— 同进程两个 client 的签名互不干扰。对比两份签名时,别把 count 或随机数的不同 当成「有一个是错的」。

下一步

最后更新于 2026-10-02

On this page