TLS指纹识别通过观察HTTPS连接是如何建立的——它提供哪些密码套件及其顺序、列出哪些扩展和椭圆曲线——在发送任何请求内容之前,识别打开HTTPS连接的软件。每个TLS库和每个浏览器版本向服务器打招呼的方式略有不同,即使之后的所有内容都被加密,这个问候对服务器来说也是明文可见的。JA3及其继任者JA4将问候转换为短哈希。反机器人系统将该哈希与声称的浏览器应该产生的哈希进行比较。这就是为什么一个自称是Chrome的Python脚本,通过一个完美的住宅IP连接,仍然会被捕获:IP说“柏林的家庭用户”;TLS握手说“脚本中的OpenSSL”。
指纹的来源:ClientHello
HTTPS连接以TLS握手开始。客户端发送的第一条消息是ClientHello。它是未加密的——必须如此,因为加密尚未协商——并且包含以下内容:
- 客户端支持的TLS版本;
- 它愿意使用的密码套件列表,按优先级顺序排列;
- 一个扩展列表——SNI(主机名)、ALPN(应用协议,例如
h2、http/1.1)、支持组(椭圆曲线)、签名算法、密钥共享、会话票据等——同样按特定顺序排列; - 它支持的椭圆曲线和点格式。
这些值都不是秘密,也不是针对每个站点选择的;它们内置于TLS库和使用它的应用程序中。Chrome 128发送特定的ClientHello;Firefox发送另一种;Python的ssl模块(OpenSSL)发送第三种;Go的crypto/tls发送第四种;curl发送第五种。服务器无论如何都会看到每个连接的ClientHello——它必须看到才能回答。记录和哈希它不需要任何成本。
JA3和JA4:将握手转化为签名
JA3
由Salesforce研究人员于2017年提出,JA3将ClientHello的五个字段——TLS版本、密码套件、扩展、椭圆曲线、椭圆曲线点格式——连接成逗号分隔的十进制列表,然后对字符串进行MD5哈希。结果是一个32字符的指纹,如cd08e31494f9531f560d64c695473da9。任何人都可以从数据包捕获中计算它,并且将JA3哈希映射到已知客户端的列表(“此哈希=Windows上的Chrome 120”,“此哈希=python-requests”)被广泛共享。
JA3有一个弱点,后来成为规避的功能:因为它按顺序哈希扩展列表,而Chrome(自版本110起)随机化每次连接的扩展顺序,因此单个Chrome安装会产生许多不同的JA3哈希。这是故意做的,以阻止服务器固化在一个指纹上——这使JA3在识别浏览器方面变得不那么有用。
JA4
JA4由FoxIO于2023年发布,旨在应对这一点。它在哈希之前对密码和扩展列表进行排序(因此Chrome的洗牌不再重要),保持某些字段可读,并纳入更多信号:连接是通过TCP还是QUIC、SNI类型、ALPN值、TLS版本以及密码和扩展的数量。JA4指纹看起来像t13d1516h2_8daaf6152771_e5627efa2ab1:第一个块表示“TCP上的TLS 1.3,15个密码,16个扩展,ALPN h2”,另外两个是排序密码列表和排序扩展列表加签名算法的截断哈希。JA4实际上是一个家族(用于TLS的JA4、用于HTTP头的JA4H、用于服务器端的JA4S、用于证书的JA4X等);当反机器人供应商今天说“TLS指纹”时,他们通常指的是JA4或类似构建的专有等价物。
为什么代理无法帮助
代理转发TLS握手;它不会重写它。使用HTTP代理时,客户端发送CONNECT host:443,代理打开TCP隧道,客户端的自己的ClientHello通过隧道原封不动地到达目的地。使用SOCKS5时,同样的事情发生在更底层。因此,目标服务器看到两个独立的事实:
- 源IP——代理的,住宅且无瑕疵;
- ClientHello——客户端的,准确说明是哪个库构建的。
然后,反机器人逻辑问一个简单的问题:TLS指纹是否与User-Agent匹配?User-Agent头声称“Windows上的Chrome 128”。JA4说“通过Python的OpenSSL 3.x”。真正的Chrome永远不会产生那个JA4。请求被标记——不是因为IP不好,而是因为故事内部不一致。这是“干净”的住宅代理在受保护网站上仍然获得CAPTCHA的最常见原因,也是为什么我们说代理回答来自哪里,而不是谁;参见代理做什么和不隐藏什么。
代理可以在一个方面起作用:一些提供商自己终止TLS(一个“中间人”代理,解密并重新加密,要求你安装其证书)。在这种情况下,目的地看到的是代理的ClientHello,而不是你的——这通常更糟,因为它是服务器端库,而不是浏览器。普通代理不会这样做。
每种客户端看起来像什么
| 客户端 | 其TLS指纹表示什么 | 与浏览器User-Agent一致? |
|---|---|---|
| 真正的Chrome / Firefox / Safari | 该浏览器和版本,在该操作系统上 | 是——它就是浏览器 |
Python requests、httpx、aiohttp | 由Python的ssl模块配置的OpenSSL | 否——无论头如何,都可识别为脚本 |
Node.js fetch / axios | Node的OpenSSL/BoringSSL构建 | 否 |
Go net/http | Go自己的TLS栈;独特 | 否 |
| curl(默认构建) | 构建时的OpenSSL/LibreSSL | 否 |
| 通过Puppeteer/Playwright的无头Chrome | 真正的Chrome——正确的TLS | TLS是;其他信号(自动化标志、缺少插件)可能仍然泄漏 |
| “模拟”客户端(curl-impersonate、curl_cffi、tls-client) | 所选浏览器的ClientHello,逐字节复现 | 是,如果你也将头与同一浏览器匹配 |
如何拥有一致的指纹
有三条诚实的路线,按成本递增。
- 使用真正的浏览器。使用Playwright或Puppeteer自动化Chrome或Firefox;TLS则真正是浏览器的。比原始HTTP更慢更重,但对于受保护的网站,这是基线。将每个浏览器上下文与自己的代理配对——参见Playwright、Puppeteer和Selenium中的代理。
- 使用模拟HTTP客户端。诸如curl_cffi(Python,基于curl-impersonate)和tls-client之类的库复现特定浏览器的ClientHello和HTTP/2设置,因此你获得爬虫速度与浏览器指纹。然后你必须保持所有其他头与同一浏览器版本一致——User-Agent、
Accept-Language、sec-ch-ua客户端提示——否则你用一个矛盾换另一个。我们的Python代理指南展示了从requests切换到curl_cffi。 - 停止假装。如果目标提供官方API,请使用你的真实客户端身份。TLS指纹识别只有在指纹与声明矛盾时才是问题。
什么不起作用:在requests上设置Chrome User-Agent。它更改一个头,但保持握手不变——不匹配现在比以前更尖锐,因为带有OpenSSL指纹的普通python-requests UA至少是自洽的。
更大的图景:指纹堆叠
TLS是一层。现代反机器人供应商还指纹识别HTTP/2连接(SETTINGS帧值、窗口大小、头顺序和伪头顺序——有时称为Akamai指纹识别)、TCP/IP栈(窗口大小、TTL、选项;经典p0f),当然还有通过JavaScript的浏览器本身,HTTP客户端根本无法运行。每一层必须与其他层和IP讲述相同的故事。具有浏览器TLS指纹和浏览器HTTP/2设置但没有JavaScript执行的住宅IP,在期望JS挑战运行的网站上仍然是异常。这就是为什么“哪个代理避免检测?”的诚实答案是“没有一个单独能”——以及为什么我们的排名权衡提供商在IP之外做了什么;参见精英代理比较。
如何查看自己的指纹
几个公共页面回显它们为你的连接计算的JA3/JA4,以及它们认为属于哪个浏览器。从真正的浏览器加载一个,然后通过相同的代理用你的脚本获取它,并比较。如果两者不同,你就找到了你的脚本与浏览器处理方式不同的原因——并且没有代理更改会修复它。每当目标已知运行企业反机器人时,将此添加到如何测试代理中的检查中。
常见问题
什么是JA3?
来自TLS ClientHello的五个字段(版本、密码、扩展、曲线、点格式)的MD5哈希,用于识别客户端软件。它适用于脚本和库;一旦Chrome开始随机化扩展顺序,它就对Chrome变得不可靠。
什么是JA4?
JA3的继任者:它在哈希之前对列表进行排序,因此扩展洗牌不会改变它,保持部分指纹可读,并包括传输(TCP/QUIC)、ALPN和计数。它是当前大多数TLS指纹检测的基础。
住宅代理会改变我的TLS指纹吗?
不会。代理隧道传输你的握手不变;目的地看到代理的IP和你的ClientHello。只有客户端软件决定指纹。
我可以在Python中伪造浏览器的TLS指纹吗?
可以,使用模拟客户端,如curl_cffi,它复现所选浏览器的ClientHello和HTTP/2行为。你必须保持头与同一浏览器一致。普通的requests/httpx无法做到这一点。
TLS指纹识别与浏览器指纹识别相同吗?
不。TLS指纹识别读取加密连接的握手并识别客户端软件;浏览器指纹识别运行JavaScript并识别设备(画布、字体、GPU)。反机器人系统使用两者,加上IP信誉,并检查它们是否一致。
为什么同一个脚本在一个站点被阻止而在另一个站点不被阻止?
因为TLS指纹检查是企业反机器人产品的功能,而不是整个网络的功能。没有此类保护的站点从不查看握手;有此类保护的站点在每个请求上将其与User-Agent进行比较。