JA3/JA4 指纹是什么?改了 UA、换了 IP 还是被认出来的问题所在

0 阅读5分钟

写脚本、做接口测试的人大概都遇到过这种时刻:User-Agent 认认真真改成了 Chrome 的、代理挂了、IP 也换了,对面还是能把"这不是浏览器"认出来。一开始我以为是哪里没改干净,查了一圈才发现——改掉的那些都不算数,真正"出卖"你的是 TLS 握手时发出去的那串东西。它有个名字:TLS 指纹,现在最常用的两个标准是 JA3 和 JA4。

这篇把它讲清楚:指纹是怎么算出来的、为什么改 UA 换了 IP 都没用、它被拿来干什么、以及在抓包工具里怎么查看吧。

一、指纹是怎么算出来的

先说 JA3(2017 年提出,也是流传最广的一个)。它的做法朴素得有点意外:把客户端在 TLS 握手第一步(ClientHello)里发的五个字段拉出来,按固定格式拼成一串字符串,再取个 MD5——那串十六进制就是你的"指纹"啊。名字里的"3"也算个小掌故:JA1、JA2 都没流行起来,到第三代才成了业界的通用做法。五个字段是这些:

组成字段里面装的是什么
TLS 版本客户端声明支持的版本
加密套件列表客户端支持的密码套件(顺序也参与计算)
扩展列表各种 TLS 扩展的类型编号
椭圆曲线支持的椭圆曲线
曲线格式点的压缩格式

拼出来大致长这样(示意,省略了大部分值):

771,4865-4866-4867-49195-49199-...,0-11-10-35-22-23,...,29-23-24,0
→ MD5 → a0e9f5d6b1c8...

关键在于:不同客户端,这五个字段的组合不一样。Chrome 是 Chrome 的一套默认值,Safari 是 Safari 的,curl、Python requests、各家 App 里打包的 SDK,各有各的写法——因为它们的 TLS 库(OpenSSL、BoringSSL、Secure Transport 等等)实现不同、默认参数不同。"写法"决定了指纹,同一种客户端在同一版本下,每次连接的指纹基本都是稳定的。

二、为什么改 UA、换 IP 都没用

这是最容易产生误解的地方。User-Agent、Cookie、请求头这些是应用层的东西——你的代码想写什么就写什么,改起来零成本。而 JA3/JA4 描述的其实是握手层的行为:你的 TLS 库在建立连接时怎么发字段,是库的实现决定的,不在你的业务代码触及的范围内。

所以局面就很清楚了嘛:对面把这个指纹和"已知客户端库"的指纹库一对——UA 写的是 Chrome 124,指纹却是 curl 或 Python 请求库的默认值,两个信息一矛盾,答案自己就浮出来了。加密套件的排列顺序也参与计算,这也是为什么同一个客户端改了默认配置之后,指纹会跟着变。指纹之所以有用,恰恰因为它不随 IP、UA 这些东西变化——IP 是租来的、UA 是能改的,握手的"笔迹"却跟着客户端实现在走。想改变它,得动 TLS 库这一层,那就不是改参数的事了。

三、它被拿来干什么

理解了原理,用途就顺了:

识别客户端类型。 最直接的用法:判断"对面声称自己是谁"和"它实际上是什么"是否一致。风控、反爬、安全分析都在用这个信号,理由上面说过了——应用层可以撒谎,握手层很难。

流量溯源与安全分析。 恶意样本通信有不少用固定 TLS 实现的,指纹就成了关联样本家族的一条线索;安全团队把它当流量特征的一环来用。Zeek 这类网络安全分析工具也把 JA3/JA4 纳入了流量特征的采集范围。

排查自家客户端的问题。 这个用途挺冷门,但很实在:App 或 SDK 升级了 TLS 库版本,指纹跟着变了,如果服务端恰好有基于指纹的策略,就可能出现"升级完突然连不上"的怪事。这时候对照握手字段和指纹看看,方向就有了。做客户端兼容性测试的话,还可以顺带确认新版本的指纹和旧版是否一致——不一致,就要评估服务端策略那边会不会有反应。

在 TraceEagle 里,查看方式很直接:点开任意一条 HTTPS 连接的详情,JA3(连原始串一起)和 JA4 都在,一键复制——排查时不用自己照着算法拼。

四、JA4 又是什么

JA3 用了几年之后,社区在 2023 年推出了它的现代化替代:JA4。设计上做了几处改进——指纹字符串带可读的前缀(标明协议、版本、有没有 SNI),算哈希从 MD5 换成截断的 SHA256,对密码套件顺序的处理也更合理;还配套了服务端方向(JA4S)和 HTTP 层(JA4H)的系列标准。

现状是两者并存:JA3 的生态积累更久、被更多工具支持,JA4 的设计更现代,新一点的方案在往它迁。具体到查看,TraceEagle 的连接详情里两个指纹都给——用哪个看你的分析对象那边认哪个呢。

五、顺理成章

看到这里,"改了 UA 还是被认出来"就不再是个谜了:其实没伪装好是因为伪装错了地方——应用层好改,握手层不好改,而识别方恰好盯着后者。

指纹这套东西的定位也值得记一下:它是"识别"用的,不是"认证"用的——同一个指纹可能对应成千上万个安装量,它能告诉你"对面大概是什么客户端",但单凭它可认不出"具体是哪一台"。理解到这一层,分析流量时该用它做什么、不该指望它什么,心里就有数了。