深入了解 TLS 指纹的工作原理:JA3 与 JA4 如何在网络层通过握手特征识别机器人和中间人代理,以及如何测试并查看你自己的 TLS 握手指纹。
每一个 HTTPS 连接都始于一次握手。在任何应用数据流动之前,你的浏览器和服务器要先协商使用哪些加密算法——而你的浏览器完成这次协商的方式,会产生一个如同 Canvas 哈希或 WebGL 哈希一样独特的指纹。TLS 指纹识别从握手中提取这个签名,来识别发出请求的客户端软件,完全不依赖 JavaScript 或 Cookie。在更下一层,早在 TLS 开始之前,TCP/IP 指纹识别就已从 TCP 握手中暴露了你的操作系统;TLS 指纹识别则从加密层接续展开。
核心要点
- TLS ClientHello 就是指纹。 当你的浏览器打开一个 HTTPS 连接时,它会发送一个 ClientHello,列出它所支持的密码套件、扩展和椭圆曲线——顺序由 TLS 库决定,而非由你决定。
- JA3 将 ClientHello 中的五个字段哈希成一个 32 个字符的 MD5 字符串;相同操作系统上的相同浏览器版本会产生相同的 JA3 哈希。
- JA4 以一种人类可读、可排序的格式改进了 JA3,能够抵御 GREASE 随机化和扩展重排序。
- 服务器将 TLS 指纹与 User-Agent 请求头结合,以捕获伪装的机器人:一个声称是 Chrome 却携带 Python
requests握手的请求,是立即引发警觉的红旗。 - 更改你的 TLS 指纹需要替换或重新配置 TLS 库本身——浏览器设置和扩展无法触及它。
赶时间? 查看你自己的实时 TLS 指纹——直接从这次连接读取你的 JA4、密码套件和 ClientHello。
什么是 TLS 指纹识别?
HTTPS 流量是加密的,但建立连接的过程并非如此。为了建立加密会话,客户端会以明文发送一个 ClientHello 消息,其中包含:
- 一个旧版 TLS 版本字段(通常为
0x0303;TLS 1.3 客户端在supported_versions扩展中指示其真实的最高版本,JA3 并不读取该字段) - 它愿意使用的密码套件列表,按偏好顺序排列
- 扩展列表(服务器名称指示、支持的组、密钥共享、ALPN 等)
- 用于密钥交换的支持椭圆曲线(命名组)
- 支持的椭圆曲线点格式
这些字段由链接到应用程序的 TLS 库决定——而非由用户、操作系统或所访问的域名决定。Chrome 的 BoringSSL、Firefox 的 NSS、Safari 的 Secure Transport 以及 Python 的 OpenSSL 绑定,各自产生可识别的不同 ClientHello。TLS 指纹识别读取这些差异,并将其转化为标识符。
TLS 1.3 标准(RFC 8446)规定了一组必须支持的扩展,但把可选扩展的顺序以及在允许集合中选择密码套件的权利留给了具体实现——而这恰恰是指纹存在的地方。
JA3:第一个被广泛采用的指纹
JA3 由 Salesforce 工程师(John Althouse、Jeff Atkinson 和 Josh Atkins)于 2017 年发布,作为一种简单、可部署的方式,在网络层对 TLS 客户端进行指纹识别,无需在客户端运行任何代码。它通过从 ClientHello 中提取五个字段并以逗号分隔的方式拼接来实现:
TLSVersion,Ciphers,Extensions,EllipticCurves,EllipticCurvePointFormats
多值字段用连字符连接。最终的字符串被 MD5 哈希,生成一个 32 字符的指纹,例如 769a07eb7f17701dd0ad09b9d04ee785。
JA3 的一些特性使其既有用又有局限:
为何有效: 哈希在会话间是稳定的。相同操作系统上相同 Chrome 版本总是产生相同的 JA3,因为 TLS 库在请求间不会改变。
为何失效: JA3 会捕获扩展的顺序和密码的顺序,因此一个随机化排序的实现——或一个新增了某个密码的浏览器更新——就会改变哈希。MD5 输出也是不透明的:你仅从指纹本身无法判断它属于浏览器还是脚本库。当浏览器新增对某种扩展类型的支持时,即便其底层身份不变,其 JA3 也会随之改变。
JA4:更健壮的替代方案
JA4 于 2023 年由 FoxIO(JA4+ 套件,由 John Althouse 领导)发布,旨在解决 JA3 的脆弱性。核心设计变化是排序:密码套件和扩展在哈希之前按其数值(十六进制)进行排序,因此重新排序不会改变指纹。GREASE 值——从 RFC 8701 定义的固定 GREASE 代码点集合中抽取的保留伪代码点,浏览器发送这些值以防止中间设备僵化拒绝未识别字段——在哈希之前被过滤掉,因此刷新了 GREASE 选择的 Chrome 版本不会使现有签名失效。
一个 JA4 指纹由一个人类可读的前缀和两个哈希组件组成:
t13d1516h2 _ 8daaf6152771 _ e5627ecdbe6c
│ │ └── 排序后扩展列表(已去除 GREASE 和 SNI)
│ │ 加签名算法的截断 SHA-256
│ └────────────────── 排序后密码套件的截断 SHA-256
└───────────────────────────────── 可读前缀:t = TLS,13 = TLS 1.3,
d = 存在 SNI,15 = 密码数量,
16 = 扩展数量,h2 = ALPN(HTTP/2)
仅凭前缀,你就能了解 TLS 版本、是否存在服务器名称指示、密码套件和扩展的数量,以及协商的 ALPN——无需参考数据库即可理解指纹含义。两个哈希后缀(密码列表,然后是扩展列表加签名算法)进一步缩小到具体实现。
JA3 与 JA4 对比一览
| 属性 | JA3 | JA4 |
|---|---|---|
| 输出格式 | MD5 哈希(32 字符,不透明) | 人类可读前缀 + 两段哈希 |
| 顺序敏感性 | 是——重排序会改变哈希 | 否——哈希前先排序 |
| GREASE 处理 | 包含,更新时哈希会变 | 哈希前已过滤 |
| TLS 版本可见性 | 否 | 是(在前缀中) |
| 部署年份 | 2017 | 2023 |
在实践中,安全工具和 CDN 边缘网络开始将两者并排记录。
有一个字段正在被这两种算法悄然改写:后量子密钥交换。像 X25519MLKEM768 这样的混合组,正在进入主流浏览器的 supported_groups 和 key_share 扩展中;由于 JA4 在哈希前会排序而 JA3 不会,这两种算法对这一变化的吸收方式截然不同——具体细节参见 ML-KEM 如何改变你的 ClientHello 指纹。
既然你已经能区分 JA3 和 JA4,下面是我们的边缘节点针对你当前连接观察到的实时 TLS 元数据——版本、密码套件,以及(在可用时)JA3/JA4 哈希——全程不作任何存储:
你的实时 TLS 指纹
读取自本次连接的 ClientHello —— 不做任何存储。
正在读取你的 TLS 握手…
JA3/JA4 哈希需要在边缘做深度包检测;此处显示的是本次连接实际暴露的字段。
需要关注的信号是一致性:你的指纹是否与你的 User-Agent 声称的浏览器相匹配?如果不匹配——比如一次看起来像 Python requests、却带着 Chrome User-Agent 的握手——就说明你网络路径中的某个东西(企业代理、安全设备或检查工具)正在改写你的握手。
服务器如何使用 TLS 指纹
机器人和自动化检测
最常见的用途是捕获声称是浏览器的自动化工具。Python 的 requests 库、Go 的 net/http、curl 和 Scrapy 各自拥有独特的 TLS 握手。当一个请求携带 User-Agent: Mozilla/5.0 (Chrome ...) 请求头,但其 JA3 哈希与 Python 的 OpenSSL 绑定相匹配时,这种不匹配就是伪装的直接证据。
这是机器人检测技术指南中所述指纹不一致检查的网络层对应物。原理相同:如果两个可观察层相互矛盾,其中一个就是在说谎。TLS 指纹识别比基于 JavaScript 的检查具有一个特殊优势——它在页面加载之前就在原始 TCP 流上运行,使其对机器人不可见。同样有效的做法是,服务器还可以检查 HTTP/2 连接指纹——SETTINGS 值、流量控制窗口增量和伪标头顺序——为检测增加第二个网络层维度:成功模拟 Chrome TLS 握手的机器人,仍可能被其库在第一个请求之前发出的 HTTP/2 帧所暴露。
VPN 和代理检测
普通的隧道 VPN(WireGuard、OpenVPN、IPSec)不会改变你的 TLS 指纹:你的浏览器仍然建立自己的 TLS 连接,VPN 仅在 IP 层包裹该流量,因此目标服务器看到的是你浏览器真实的 ClientHello。TLS 指纹实际上捕获的是拦截行为。HTTPS 检查代理——以及代你终止 TLS 的 VPN 应用——使用自己的 TLS 库,产生与任何普通浏览器不同的握手。例如,企业 HTTPS 代理会重新加密流量,并呈现一个新的 ClientHello,通常与企业安全产品而非浏览器匹配。检测系统可以将此标记为连接正在被拦截的证据。参阅网站如何检测 VPN 和代理,全面了解 TLS 指纹之外还会检查哪些内容,包括 IP 信誉、地理不匹配和 WebRTC 泄露。
安全监控
在防御侧,TLS 指纹识别有助于检测与命令和控制服务器通信的恶意软件。大多数现成的恶意软件和漏洞利用框架使用通用库(Python、Go、.NET),即使域名或 IP 地址随活动而改变,也会产生可识别的 JA3。网络入侵检测系统会记录 JA3/JA4 哈希,并在已知恶意签名出现在网络上时发出警报。
测试你自己的 TLS 指纹
将上方的实时指纹与你认为自己正在运行的浏览器进行对比。BrowserInsight 的机器人检测工具从浏览器内部暴露相应的信号——自动化痕迹和无头浏览器泄露——而 JA3/JA4 这类网络层数值则需要上文展示的服务器端检查,因为它们是从浏览器在任何 JavaScript 运行之前发送的数据中计算出来的。
缓解措施:更改你的 TLS 指纹
与浏览器指纹识别不同——后者可以通过切换浏览器或启用隐私模式来部分减少——更改 TLS 指纹需要更改 TLS 库本身,因为指纹是在任何用户代码运行之前由库生成的。
直接使用目标浏览器。 最简单也最可靠的方法。Chrome 的指纹是网络上预期最广泛的签名,使用未修改的 Chrome 安装可确保你的 TLS 握手与服务器对你的 User-Agent 的预期相匹配。
使用 TLS 模拟库。 curl-impersonate 等工具会重排密码套件和扩展,以完全匹配目标浏览器的握手。它们主要被需要通过 TLS 层检查的抓取操作人员使用。
使用由真实浏览器终止的住宅代理。 某些代理服务通过由实际浏览器引擎支持的管道转发请求,端到端保留浏览器的 TLS 指纹。
对于大多数普通用户来说,TLS 指纹识别是不可见的,无需任何操作。当你运行的自动化程序需要通过真实浏览器的检查,或者你想了解为什么网络设备或 CDN 会标记你的流量时,它才变得相关。
常见问题
TLS 1.3 会让指纹识别更难吗?
在一定程度上会。TLS 1.3 加密了比 1.2 更多的握手内容,包括服务器的证书——但 ClientHello 本身因为在共享密钥存在之前服务器必须读取它,所以必然是明文的。ClientHello 仍然包含足够的变异性来对客户端进行独特的指纹识别,而 JA4 正是专为 TLS 1.3 设计的。一个新兴扩展确实改变了这一格局:加密客户端问候(ECH) 使用服务器在 DNS 中发布的密钥来加密敏感的内部 ClientHello。在 ECH 已部署的地方(Chrome 和 Cloudflare 自 2023-2024 年以来已支持),网络观察者只能看到一个最小化的外部 ClientHello,这限制了可以被指纹识别的内容。
TLS 指纹识别合法吗?
在典型情境下,是的。服务器历来被允许读取发送给它们的流量,而 ClientHello 是必须因协议设计而以明文存在的交换部分。使用该数据对客户端进行分类,与分析 HTTP 请求头没有什么不同。
HTTPS 检查代理能隐藏原始指纹吗?
不能——它用不同的指纹替换原始指纹。解密并重新加密 HTTPS 流量的代理会呈现自己的 ClientHello,通常与代理软件的 TLS 库匹配,而非用户的浏览器。检查预期指纹的检测系统会看到代理签名,这本身通常也是一个标志。
什么是 GREASE,它为何会影响 JA3?
GREASE(Generate Random Extensions And Sustain Extensibility,生成随机扩展并维持可扩展性)是 RFC 8701 中定义的一种机制,TLS 实现会在 ClientHello 中通告保留的伪值——从固定的 GREASE 代码点集合中抽取。其目的是确保服务器和中间设备不会仅仅因为遇到未识别字段就拒绝连接——保持协议的可扩展性。由于 JA3 原样包含这些 GREASE 值,Chrome 更新其 GREASE 选择就会改变 JA3 哈希,即便功能上没有任何变化。JA4 通过在哈希前过滤掉 GREASE 值来解决这一问题。


