Skip to content

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 实现的差异体现在:

  1. 密码套件的选择:Go 标准库默认的套件列表与 Chrome 不同
  2. 密码套件的顺序:有些实现在 ClientHello 中按照特定顺序排列套件
  3. 扩展的类型和数量:不同实现支持不同的扩展
  4. 扩展的顺序:JA3 会考虑扩展的顺序
  5. 椭圆曲线的选择:不同实现支持的曲线不同

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,0
  • 769: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 的局限性

  1. GREASE 问题:Google 的 GREASE 机制在 ClientHello 中插入随机扩展值,每次连接不同的 JA3。JA3 通过忽略 GREASE 值来处理,但当密码套件列表被随机化时(如 Go 1.21+ 的 TLS 1.3),JA3 会为同一客户端产生多个不同指纹
  2. MD5 碰撞:理论上不同 TLS 握手可能产生相同的 MD5 哈希
  3. 不支持 QUIC:JA3 只覆盖 TCP 上的 TLS
  4. 无法反映 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 的改进

特性JA3JA4
抗 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。它支持模拟以下浏览器的指纹:

指纹名称模拟对象
chromeGoogle Chrome 最新版本
firefoxMozilla Firefox
safariApple Safari
iosiOS 设备的 TLS 实现
androidAndroid 设备的 TLS 实现
edgeMicrosoft Edge
360360 浏览器
qqQQ 浏览器
randomized随机化指纹(每次不同)

在 REALITY 中的集成

源码位置:xray-core/transport/internet/reality/reality.go 行 117-176

REALITY 客户端使用 uTLS 的关键步骤:

go
// 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)

关键点:

  1. uTLS 构造与 Chrome/Firefox 等浏览器完全一致的 ClientHello
  2. REALITY 在 uTLS 构造的 ClientHello 基础上,修改 Session ID 字段嵌入认证信息
  3. 认证信息嵌入应尽可能少地改变原始 ClientHello 结构,避免产生新的指纹特征
  4. 客户端从 REALITY 服务端收到的是临时自签证书(非 CA 签发),InsecureSkipVerify: true 跳过标准证书验证,由 VerifyPeerCertificate 回调进行自定义验证

GFW 如何利用 TLS 指纹

  1. 识别 Go 标准库的 TLS 流量:Go 默认的 crypto/tls 指纹 b32309a26951... 是公开已知的,几乎所有使用 Go 标准 TLS 的代理工具都有相同指纹
  2. 与白名单比对:如果某个 IP 的 443 端口大量收到 Go 指纹的 TLS 连接,且该 IP 不是已知的 API 服务或网站,则标记为可疑
  3. 结合 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 如何在模拟指纹的同时嵌入认证信息

下一步阅读