TLS 指纹(JA3/JA4)机制
简介
本文介绍 TLS 客户端指纹的原理、JA3 和 JA4 两种主流指纹算法,以及 uTLS 库如何模拟不同浏览器的 TLS 指纹来对抗基于指纹的检测。
本文回答
- 什么是 TLS 客户端指纹
- JA3 和 JA4 分别如何计算
- GFW 如何使用 TLS 指纹进行检测
- uTLS 如何模拟和伪造 TLS 指纹
本文在项目中的位置
本文属于背景知识层。理解 TLS 指纹是理解 REALITY 客户端为什么需要 uTLS 的前置条件。
适合读者
所有想理解 TLS 层面检测和反检测技术的读者。
前置知识
TLS 握手的基本流程,特别是 ClientHello 消息的结构。
文章理解难度
中等。需要理解 TLS 协议的扩展字段和密码套件协商机制。
重点
- JA3 的五个组成字段及其含义
- JA4 相比 JA3 的改进
- uTLS 的工作原理和 REALITY 中的集成方式
- 为什么 Go 标准库的默认 TLS 指纹是独特的
上级文档和相关文档
主要证据
Salesforce JA3 技术博客,JA4+ 官方文档,uTLS 源码。
TLS ClientHello 结构与指纹来源
为什么 ClientHello 可以用于指纹识别
TLS 握手的第一步是客户端发送 ClientHello 消息。这个消息虽然是加密握手的前奏,但本身以明文形式传输(在 ECH 出现之前或 ECH 未启用时)。
ClientHello 包含以下用于指纹识别的关键字段:
ClientHello:
├── Protocol Version # 支持的 TLS 版本
├── Random # 32 字节随机数(不用作指纹)
├── Session ID # 会话 ID
├── Cipher Suites # 支持的密码套件列表(有顺序)
├── Compression Methods # 支持的压缩方法
└── Extensions # TLS 扩展(以下为关键扩展)
├── server_name (SNI) # 目标域名
├── supported_groups # 支持的椭圆曲线/密钥交换组
├── ec_point_formats # 椭圆曲线点格式
├── signature_algorithms # 签名算法
├── ALPN # 应用层协议(h2, http/1.1 等)
├── supported_versions # 支持的 TLS 版本(TLS 1.3)
├── key_share # 密钥共享(TLS 1.3)
└── ...不同 TLS 实现的差异体现在:
- 密码套件的选择:Go 标准库默认的套件列表与 Chrome 不同
- 密码套件的顺序:有些实现在 ClientHello 中按照特定顺序排列套件
- 扩展的类型和数量:不同实现支持不同的扩展
- 扩展的顺序:JA3 会考虑扩展的顺序
- 椭圆曲线的选择:不同实现支持的曲线不同
Go 标准库的 TLS 指纹问题
Go 的 crypto/tls 库有独特且固定的 ClientHello 格式,其特征包括:
- 密码套件列表中包含 Go 特有的选择
- 扩展字段组合与任何主流浏览器不同
- 在 TLS 1.3 中,Go 客户端默认对密码套件进行随机排序(GREASE 风格),导致每次指纹不同(这在 JA4 中被修正)
这使得基于 Go 标准库的代理工具极易被指纹识别。
JA3 指纹算法
JA3 由 Salesforce 在 2017 年提出,是使用最广泛的 TLS 指纹算法。
计算方法
JA3 从 ClientHello 中提取五个字段,组装成一个字符串后进行 MD5 哈希:
JA3 = MD5(
TLSVersion,
CipherSuites,
Extensions,
SupportedCurves,
ECPointFormats
)字节表示例:
769,47-53-5-10-49161-49162-49171-49172-50-56-19-4,0-10-11,23-24-25,0769:TLS 版本(0x0301 = TLS 1.0,但 769 是十进制表示的变体)47-53-5-10-49161-49162-49171-49172-50-56-19-4:密码套件 ID 列表(用-连接,按原始顺序)0-10-11:扩展 ID 列表23-24-25:支持的椭圆曲线 ID 列表0:EC 点格式
JA3S(Server Hello 指纹)
JA3S 是对服务端 ServerHello 的指纹,提取字段略有不同:
JA3S = MD5(
TLSVersion,
CipherSuite, # 选定的单个密码套件
Extensions
)JA3 + JA3S 组合可以提供非常精确的连接识别。
JA3 的局限性
- GREASE 问题:Google 的 GREASE 机制在 ClientHello 中插入随机扩展值,每次连接不同的 JA3。JA3 通过忽略 GREASE 值来处理,但当密码套件列表被随机化时(如 Go 1.21+ 的 TLS 1.3),JA3 会为同一客户端产生多个不同指纹
- MD5 碰撞:理论上不同 TLS 握手可能产生相同的 MD5 哈希
- 不支持 QUIC:JA3 只覆盖 TCP 上的 TLS
- 无法反映 TLS 1.3 特有字段:如 key_share、signature_algorithms 等
JA4 指纹算法
JA4 是 JA3 的改进版本,由 FoxIO 在 2023 年发布。它解决了许多 JA3 的局限性。
JA4 格式
JA4 = Protocol_CipherHash_ExtensionHash_SignatureAlgorithms示例:
t13d1516h2_8daaf6152771_e1f2c3a4b5_0005各部分含义:
t13:TLS 1.3(t12= TLS 1.2,t10= TLS 1.0)d:使用了域名 SNI(i= 仅 IP 地址)15:密码套件数量16:扩展数量h2:ALPN 为 HTTP/2(h1= HTTP/1.1)8daaf6152771:密码套件列表排序后的截断 SHA256 哈希e1f2c3a4b5:扩展列表排序后的截断 SHA256 哈希0005:签名算法标识
JA4 相比 JA3 的改进
| 特性 | JA3 | JA4 |
|---|---|---|
| 抗 GREASE 随机化 | 通过忽略已知 GREASE | 通过排序消除顺序随机化 |
| 人类可读性 | 需要额外解析 | 前缀包含元数据描述 |
| QUIC 支持 | 不支持 | 支持(q 前缀) |
| 签名算法 | 不包含 | 包含 |
| ALPN 信息 | 不包含 | 包含 |
| 哈希方法 | MD5(有碰撞风险) | 截断 SHA256(12 字符) |
JA4+ 系列
JA4 是一个更大的指纹系列的一部分:
- JA4:TLS 客户端指纹
- JA4S:TLS 服务端指纹
- JA4H:HTTP 客户端指纹(方法、版本、Cookie、Accept 等 Header 组合)
- JA4X:X509 TLS 证书指纹
- JA4SSH:SSH 会话指纹
uTLS:TLS 指纹模拟
uTLS(github.com/refraction-networking/utls)是一个 Go 语言的 TLS 库,在标准 crypto/tls 基础上增加了 ClientHello 指纹模拟能力。
工作原理
uTLS 替换了 Go 标准库中构造 ClientHello 的逻辑,可以按照预设的指纹模板生成 ClientHello。它支持模拟以下浏览器的指纹:
| 指纹名称 | 模拟对象 |
|---|---|
chrome | Google Chrome 最新版本 |
firefox | Mozilla Firefox |
safari | Apple Safari |
ios | iOS 设备的 TLS 实现 |
android | Android 设备的 TLS 实现 |
edge | Microsoft Edge |
360 | 360 浏览器 |
qq | QQ 浏览器 |
randomized | 随机化指纹(每次不同) |
在 REALITY 中的集成
源码位置:xray-core/transport/internet/reality/reality.go 行 117-176
REALITY 客户端使用 uTLS 的关键步骤:
// 1. 获取指定指纹
fingerprint := tls.GetFingerprint(config.Fingerprint)
// 2. 使用 uTLS 创建连接(而非标准 crypto/tls)
uConn.UConn = utls.UClient(c, utlsConfig, *fingerprint)
// 3. 手动构建握手状态,以便修改 Session ID
uConn.BuildHandshakeState()
hello := uConn.HandshakeState.Hello
hello.SessionId = make([]byte, 32)
// ... 在 Session ID 中嵌入 REALITY 认证信息 ...
copy(hello.Raw[39:], hello.SessionId)关键点:
- uTLS 构造与 Chrome/Firefox 等浏览器完全一致的 ClientHello
- REALITY 在 uTLS 构造的 ClientHello 基础上,修改 Session ID 字段嵌入认证信息
- 认证信息嵌入应尽可能少地改变原始 ClientHello 结构,避免产生新的指纹特征
- 客户端从 REALITY 服务端收到的是临时自签证书(非 CA 签发),
InsecureSkipVerify: true跳过标准证书验证,由VerifyPeerCertificate回调进行自定义验证
GFW 如何利用 TLS 指纹
- 识别 Go 标准库的 TLS 流量:Go 默认的
crypto/tls指纹b32309a26951...是公开已知的,几乎所有使用 Go 标准 TLS 的代理工具都有相同指纹 - 与白名单比对:如果某个 IP 的 443 端口大量收到 Go 指纹的 TLS 连接,且该 IP 不是已知的 API 服务或网站,则标记为可疑
- 结合 SNI 检测:即使指纹模拟为 Chrome,如果 SNI 指向的域名与 IP 归属不匹配,也可能触发检测
外部参考资料
资料:TLS Fingerprinting with JA3 and JA3S 类型:技术博客(Salesforce Engineering) 链接:https://engineering.salesforce.com/tls-fingerprinting-with-ja3-and-ja3s-247362855967 可信度:A,JA3 的原创发布者
资料:JA4+ Network Fingerprinting 类型:官方文档 链接:https://github.com/FoxIO-LLC/ja4 可信度:A,JA4 的官方仓库
资料:uTLS 项目 类型:开源项目 链接:https://github.com/refraction-networking/utls 可信度:A,REALITY 的直接依赖
读者自检
读完本文后应能回答:
- TLS 指纹是如何从 ClientHello 中提取的
- JA3 和 JA4 的核心区别
- 为什么 Go 标准库的 TLS 实现容易被检测
- uTLS 如何模拟浏览器指纹
- REALITY 如何在模拟指纹的同时嵌入认证信息
下一步阅读
- REALITY 协议详解:理解完整的握手流程
- ECH 加密客户端问候:理解 TLS 指纹的未来发展