Reality 协议底层深度剖析:从 TLS 1.3 握手、JA3/JA4 客户端指纹到 SNI 偷取伪装实战
Reality 的核心结论是:它不依赖自有域名与自签名证书,而是通过偷取真实高信誉站点的 SNI,在 TLS 1.3 握手中借用其证书链完成认证,使主动探测者无法通过证书异常识别代理。其抗封锁能力取决于三点:ClientHello 指纹与真实浏览器一致、Target SNI 站点支持 TLS 1.3 与 OCSP Stapling、服务端回落行为与真实站点不可区分。仅靠协议本身不足以防封,必须配合 JA3/JA4 指纹伪装与合规 Target 选择。
Reality 的核心结论是:它不依赖自有域名与自签名证书,而是通过偷取真实高信誉站点的 SNI,在 TLS 1.3 握手中借用其证书链完成认证,使主动探测者无法通过证书异常识别代理。其抗封锁能力取决于三点:ClientHello 指纹与真实浏览器一致、Target SNI 站点支持 TLS 1.3 与 OCSP Stapling、服务端回落行为与真实站点不可区分。仅靠协议本身不足以防封,必须配合 JA3/JA4 指纹伪装与合规 Target 选择。
Reality 协议底层深度剖析:从 TLS 1.3 握手、JA3/JA4 客户端指纹到 SNI 偷取伪装实战
引言
Reality 是 Xray-core 在 2023 年引入的一种新型 TLS 伪装方案,其设计目标直指传统 TLS 伪装方案(WebSocket+TLS、Trojan、gRPC+TLS)的三个结构性缺陷:自有域名暴露、证书链可追溯、主动探测可识别。理解 Reality 必须建立在对 TLS 1.3 ClientHello 二进制结构、JA3/JA4 指纹算法、以及 X25519 密钥交换的精确掌握之上。本文前三章从协议栈最底层开始,逐字节拆解握手报文,给出可落地的 Target 挑选与校验方法。
1. TLS 1.3 握手与 JA3/JA4 指纹生成机制
1.1 ClientHello 结构逐字段解析
TLS 1.3(RFC 8446)的 ClientHello 是明文传输的,这是所有指纹技术的物理基础。整个记录层结构如下:
TLS Record Header (5 bytes)
├── ContentType = 0x16 (Handshake)
├── legacy_version = 0x0301 (TLS 1.0, 兼容性占位)
└── length = 2 bytes
Handshake Header (4 bytes)
├── HandshakeType = 0x01 (ClientHello)
└── length = 3 bytes
ClientHello Body
├── legacy_version = 0x0303 (TLS 1.2, 固定值)
├── random[32] = 4 bytes 时间戳 + 28 bytes 随机数
├── legacy_session_id = 变长 (0~32 bytes, 中间盒兼容)
├── cipher_suites = 变长列表
├── legacy_compression = 0x01 0x00 (仅 null)
└── extensions = 变长列表 ← 指纹核心
legacy_version 字段在 TLS 1.3 中被固定为 0x0303,真实版本协商通过 supported_versions 扩展完成。legacy_session_id 在 TLS 1.3 中承担中间盒兼容职责,Chrome 自 110 版本起填充 32 字节随机值,Firefox 保持空值,这个字段的差异直接进入 JA3 的扩展拼接序列之前的指纹空间。
cipher_suites 的顺序在指纹识别中权重极高。Chrome 的典型序列以 0x1301 (TLS_AES_128_GCM_SHA256) 开头,紧随 0x1302、0x1303,然后才是 0xc02b、0xc02f 等 TLS 1.2 套件;Firefox 则把 0x1303 (ChaCha20) 放在首位(因为 Mozilla 在无 AES-NI 硬件上优先 ChaCha20)。顺序的差异在 JA3 中直接体现为 MD5 输入串的不同。
extensions 列表的顺序和内容是指纹的决定性因素。Chrome 的扩展顺序(截至 124 版本)大致为:
0x0000 server_name
0x0017 extended_master_secret
0x000d signature_algorithms
0x0010 application_layer_protocol_negotiation
0x000b ec_point_formats
0x0023 session_ticket
0x0012 signed_certificate_timestamp
0x002b supported_versions
0x0033 key_share
0x002d psk_key_exchange_modes
0x0029 pre_shared_key
0x000a supported_groups
0x001c record_size_limit
0x002a early_data
...
扩展顺序在 RFC 中并无强制约束,但真实浏览器实现由代码路径决定,顺序高度稳定。uTLS 伪造失败的最常见原因就是扩展顺序与目标浏览器不一致,即便扩展集合完全相同,顺序错位也会导致 JA3 哈希完全不同。
1.2 JA3 指纹算法
JA3(Salesforce 2017 年提出)的定义为:
JA3_string = SSLVersion,Ciphers,Extensions,EllipticCurves,EllipticCurvePointFormats
JA3_hash = MD5(JA3_string)
各字段拼接规则:
SSLVersion:ClientHello 的legacy_version,TLS 1.3 场景下恒为771(0x0303)Ciphers:cipher_suites中每个套件以十进制表示,用-连接,GREASE 值被剔除Extensions:extensions中每个扩展类型以十进制表示,用-连接,GREASE 剔除EllipticCurves:supported_groups扩展内的曲线 ID,十进制,-连接EllipticCurvePointFormats:ec_point_formats扩展内的格式值
GREASE(RFC 8701)是 Google 提出的抗 ossification 机制,在 0x0a0a 到 0xfafa 之间以 0x?a?a 模式插入占位值。JA3 算法在计算前必须剔除 GREASE,否则同一客户端每次连接指纹都会变化。
JA3 的已知碰撞缺陷有两类。第一类是顺序无关性缺失:JA3 保留了 cipher 和 extension 的顺序信息,但丢弃了扩展内部的参数内容。例如 signature_algorithms 扩展的具体签名算法列表、supported_versions 的具体版本号,都不进入 JA3 字符串。这意味着两个扩展集合相同但内部参数不同的客户端会产生相同 JA3。第二类是GREASE 剔除带来的信息损失:剔除 GREASE 后,Chrome 与某些 uTLS 模板的指纹可能碰撞。
生产环境中的 JA3 数据库(如 Salesforce 的公开数据集)显示,Chrome 的 JA3 哈希在 2024 年仍稳定在 cd08e31494f9531f560d64c695473da9 附近,但每次 Chrome 大版本更新都会漂移。
1.3 JA4 指纹体系
JA4(FoxIO 2023 年提出)针对 JA3 的缺陷重新设计,采用三段式结构:
JA4_a = [protocol][version][SNI][cipher_count][extension_count][ALPN]
JA4_b = SHA256(sorted_cipher_suites)[:12]
JA4_c = SHA256(sorted_extensions + signature_algorithms)[:12]
JA4 = JA4_a + "_" + JA4_b + "_" + JA4_c
具体编码规则:
protocol:t表示 TCP,q表示 QUICversion:13表示 TLS 1.3,12表示 TLS 1.2SNI:d表示携带 SNI 且为域名,i表示携带 SNI 且为 IP,n表示无 SNIcipher_count:两位十进制,超过 99 记为99extension_count:两位十进制ALPN:h2取首尾字符h2,http/1.1取h1,无 ALPN 取00
JA4_b 和 JA4_c 使用排序后的列表做 SHA256,这消除了顺序敏感性,同时把 signature_algorithms 纳入哈希,大幅提升了对 uTLS 伪装的识别精度。uTLS 若只复制扩展顺序而未复制签名算法列表,JA4_c 会立即暴露。
JA4 的工程价值在于:JA4_a 是明文可读的,运维可以直接在日志中 grep t13d1516h2 这类前缀快速筛选客户端类型,无需查表。JA4_b/c 的哈希碰撞概率在 48 bit 截断下对普通流量足够,但对大规模僵尸网络仍有理论碰撞空间。
1.4 uTLS 指纹伪造原理
uTLS 是 refraction-networking 维护的 Go 库,通过替换 crypto/tls 的 ClientHello 构造逻辑实现指纹伪造。核心接口是 ClientHelloID,内置模板包括 HelloChrome_120、HelloFirefox_120、HelloSafari_16_0 等。
伪造流程:
1. 加载模板 → 得到 (cipher_suites, extensions, curves, point_formats) 四元组
2. 应用 GREASE 注入策略(Chrome 模板会随机插入 GREASE)
3. 构造 ClientHello 字节流,覆盖默认实现
4. 交由底层 TLS 栈完成密钥交换
生产环境中的关键 Trade-off:uTLS 模板与真实浏览器存在版本滞后。Chrome 每 4 周发布一个大版本,uTLS 模板通常滞后 2~6 周。在这段窗口期内,使用旧模板的客户端 JA4 与真实 Chrome 不一致,会被高级 DPI 系统标记。Reality 的设计部分规避了这个问题:它不追求指纹与浏览器一致,而是追求握手在密码学层面与真实 Target 站点不可区分。
2. Reality 与传统 TLS 伪装的架构本质差异
2.1 传统方案的共性缺陷
WebSocket+TLS 与 Trojan 的架构可以抽象为:
Client ──TLS(SNI=yourdomain.com)──> Server ──> 内网代理
│
└── 证书: Let's Encrypt / 自签
这套架构有三个可被利用的探测点:
证书透明度日志(CT Log):Let’s Encrypt 签发的证书全部进入 CT Log,任何机构可以查询 yourdomain.com 的证书签发记录。GFW 的主动探测系统会定期扫描 CT Log 新增域名,对可疑域名发起 TLS 握手探测。
主动探测识别:探测者用任意 SNI 连接服务器,若服务器返回有效证书并完成握手,但后续 HTTP 请求返回异常响应(如 400、空响应、非标准 HTTP 头),则判定为代理服务器。Trojan 的应对是回落至 Nginx,但 Nginx 的响应特征(Server 头、TLS 指纹)与真实站点仍有差异。
证书链特征:自签证书的 Issuer 字段、有效期、扩展字段与商业 CA 差异明显;Let’s Encrypt 证书的 OCSP 响应由 LE 的 OCSP 服务器签发,与目标伪装站点不匹配。
2.2 Reality 的无域名架构
Reality 的核心设计是不持有域名、不申请证书。服务端在握手阶段借用 Target 站点的真实证书链:
Client Reality Server Target (e.g. www.microsoft.com)
│ │ │
│──ClientHello(SNI=www.microsoft.com, key_share=X25519_pub_C)──────────>│
│ │ │
│ │──ClientHello(SNI=www.microsoft.com)─>│
│ │<──ServerHello + Cert + CertVerify────│
│ │ │
│<──ServerHello(临时证书, 私钥由 X25519 派生)────────────────────────────│
│ │ │
│──Finished──────────────────────>│ │
│<──Finished───────────────────────│ │
│ │ │
│──应用数据(Reality 认证头)────────>│ │
服务端在收到 ClientHello 后,用 Target 的真实地址发起一次 TLS 握手,获取 Target 的证书链。然后用 X25519 派生的临时密钥对证书进行”重新签名”,实际上 Reality 使用了一种基于 X25519 的临时证书机制,服务端持有 Target 的公钥(预配置),客户端在 ClientHello 的 key_share 中嵌入认证信息。
2.3 认证机制
Reality 的认证基于 X25519 临时密钥与 Target 公钥的派生:
客户端:
ephemeral_priv_C, ephemeral_pub_C = X25519_keygen()
shared = X25519(ephemeral_priv_C, target_pub)
auth_key = HKDF(shared, "REALITY", 32)
在 ClientHello 的 session_id 字段嵌入 auth_key 的加密形式
服务端:
target_priv (预配置)
shared = X25519(target_priv, ephemeral_pub_C) ← 与客户端相同
auth_key = HKDF(shared, "REALITY", 32)
校验 session_id 中的认证信息
认证通过后,服务端接管连接,使用临时证书完成 TLS 握手。认证失败则透明转发至 Target 站点。
2.4 回落行为设计
未通过认证的探测请求被透明转发至真实 Target 站点:
Prober ──ClientHello(SNI=www.microsoft.com)──> Reality Server
│
│ 认证失败
↓
TCP 转发至 www.microsoft.com:443
│
<──真实 ServerHello + Cert──
│
Prober <──真实 Microsoft 证书 + 完整握手─────────────────
探测者得到的是 Microsoft 的真实证书和真实响应,无法区分这是 Microsoft 服务器还是 Reality 服务器。这是 Reality 相对于 Trojan 的根本性优势:回落目标不是本地 Nginx,而是真实的外部站点。
工程 Trade-off:回落转发引入额外 RTT。若 Reality 服务器与 Target 站点物理距离远(如国内服务器回落至美国 Microsoft),探测者会观察到异常高的握手延迟,这可能成为新的指纹。生产环境应选择地理邻近的 Target。
3. Target SNI 挑选实战与合规性校验
3.1 Target 站点硬性条件
Reality 服务端对 Target 的要求:
| 条件 | 原因 |
|---|---|
| 支持 TLS 1.3 | Reality 握手依赖 TLS 1.3 的 key_share 扩展 |
| 支持 X25519 | Reality 认证基于 X25519 密钥交换 |
| 启用 OCSP Stapling | 证书链完整性,避免客户端额外查询 OCSP |
| ALPN 含 h2 与 http/1.1 | 指纹一致性,避免 ALPN 不匹配暴露 |
| 证书有效期 > 90 天 | 减少证书轮换导致的可用性中断 |
| 无 CDN 前置或 CDN 支持 TLS 1.3 | CDN 可能修改握手特征 |
不满足 X25519 的站点(如仅支持 P-256 的旧站点)无法作为 Target。OCSP Stapling 缺失会导致客户端在握手后额外发起 OCSP 查询,产生可观测的流量特征。
3.2 CDN 节点合规性评估
Cloudflare:默认支持 TLS 1.3、X25519、OCSP Stapling,ALPN 含 h2。但 Cloudflare 的握手特征高度统一,大量 Reality 服务器选择 Cloudflare 前置的站点作为 Target,导致这些 SNI 的流量模式异常集中。GFW 已对部分 Cloudflare IP 段实施 SNI 白名单策略。
Akamai:TLS 1.3 支持良好,但 Akamai 的边缘节点会根据客户端地理位置返回不同证书,Reality 服务端若与客户端地理距离远,可能获取到与客户端预期不同的证书链。
Fastly:支持完整,但 Fastly 的 TLS 终止节点较少,Target 选择范围有限。
国内 CDN:阿里云 CDN、腾讯云 CDN 部分节点仍以 TLS 1.2 为主,且 OCSP Stapling 支持不完整。国内 CDN 的握手特征与海外差异明显,作为 Target 会引入额外指纹。
生产环境推荐:选择地理邻近、TLS 1.3 支持完整、流量规模大的站点。流量规模大意味着 Reality 流量可以隐藏在正常流量中。
3.3 SNI 偷取风险
Target 被墙:若 Target 站点本身被 GFW 封锁,Reality 服务器的回落行为会失败,探测者观察到连接超时,暴露服务器异常。
证书轮换:Target 站点更换证书后,Reality 服务端缓存的证书链失效,需要重新获取。Xray-core 实现了证书自动刷新,但刷新窗口期内可能出现握手失败。
IP 封禁:Reality 服务器 IP 若被 GFW 封禁,所有 Target 选择都失效。这是不可规避的风险,只能通过 IP 轮换缓解。
传导影响可以量化为可用性公式:
$$A = A_{target} \times A_{cert} \times A_{ip}$$
其中 $A_{target}$ 为 Target 站点可用性,$A_{cert}$ 为证书链有效性,$A_{ip}$ 为服务器 IP 可用性。三个因子任一为 0,整体可用性为 0。
3.4 自动化校验脚本
使用 openssl s_client 批量检测 Target 的 TLS 1.3 支持度:
#!/bin/bash
# reality-target-check.sh
# 批量检测候选 Target 站点的 TLS 1.3 / X25519 / OCSP Stapling 支持情况
# 依赖: openssl 1.1.1+, jq
TARGETS=(
"www.microsoft.com:443"
"www.apple.com:443"
"www.cloudflare.com:443"
"www.amazon.com:443"
)
for target in "${TARGETS[@]}"; do
host="${target%%:*}"
port="${target##*:}"
echo "=== Checking $host:$port ==="
# 1. 检测 TLS 1.3 支持
tls13=$(echo | openssl s_client -connect "$target" -tls1_3 -servername "$host" 2>/dev/null | grep -c "TLSv1.3")
echo " TLS 1.3: $([ "$tls13" -gt 0 ] && echo YES || echo NO)"
# 2. 检测 X25519 密钥交换
# openssl s_client -groups 参数指定曲线,若握手成功则支持
x25519=$(echo | openssl s_client -connect "$target" -tls1_3 -groups X25519 -servername "$host" 2>/dev/null | grep -c "Server Temp Key: X25519")
echo " X25519: $([ "$x25519" -gt 0 ] && echo YES || echo NO)"
# 3. 检测 OCSP Stapling
ocsp=$(echo | openssl s_client -connect "$target" -tls1_3 -status -servername "$host" 2>/dev/null | grep -c "OCSP Response Status: successful")
echo " OCSP Stapling: $([ "$ocsp" -gt 0 ] && echo YES || echo NO)"
# 4. 检测 ALPN
alpn=$(echo | openssl s_client -connect "$target" -tls1_3 -alpn h2,http/1.1 -servername "$host" 2>/dev/null | grep "ALPN protocol:")
echo " ALPN: $alpn"
# 5. 证书有效期
expiry=$(echo | openssl s_client -connect "$target" -servername "$host" 2>/dev/null | openssl x509 -noout -enddate 2>/dev/null | cut -d= -f2)
echo " Cert Expiry: $expiry"
echo ""
done
使用 tls.peet.ws 检测 JA3/JA4 指纹一致性:
#!/bin/bash
# 通过 tls.peet.ws API 获取当前客户端的 JA3/JA4 指纹
# 用于验证 uTLS 模板是否生效
curl -s https://tls.peet.ws/api/all | jq '{
ja3: .tls.ja3,
ja3_hash: .tls.ja3_hash,
ja4: .tls.ja4,
akamai: .tls.akamai_hash,
user_agent: .http_user_agent
}'
3.5 Reality 服务端配置示例
Xray-core 的 Reality 服务端配置(config.json):
{
"inbounds": [
{
"listen": "0.0.0.0",
"port": 443,
"protocol": "vless",
"settings": {
"clients": [
{
"id": "b831381d-6324-4d53-ad4f-8cda48b30811",
"flow": "xtls-rprx-vision"
}
],
"decryption": "none"
},
"streamSettings": {
"network": "tcp",
"security": "reality",
"realitySettings": {
"show": false,
"dest": "www.microsoft.com:443",
"xver": 0,
"serverNames": [
"www.microsoft.com",
"www.apple.com"
],
"privateKey": "YOUR_X25519_PRIVATE_KEY",
"shortIds": [
"0123456789abcdef"
]
}
}
}
],
"outbounds": [
{
"protocol": "freedom",
"tag": "direct"
}
]
}
字段说明:
dest:Target 站点地址,Reality 服务端从此处获取证书链serverNames:允许的 SNI 列表,客户端必须使用其中之一privateKey:X25519 私钥,由xray x25519命令生成shortIds:短 ID,用于客户端认证的辅助标识,16 进制字符串
生成密钥对:
xray x25519
# Private key: <base64>
# Public key: <base64>
3.6 客户端配置示例
sing-box 客户端配置:
{
"inbounds": [
{
"type": "tun",
"tag": "tun-in",
"inet4_address": "172.19.0.1/30",
"auto_route": true,
"strict_route": true,
"stack": "system"
}
],
"outbounds": [
{
"type": "vless",
"tag": "reality-out",
"server": "YOUR_SERVER_IP",
"server_port": 443,
"uuid": "b831381d-6324-4d53-ad4f-8cda48b30811",
"flow": "xtls-rprx-vision",
"tls": {
"enabled": true,
"server_name": "www.microsoft.com",
"utls": {
"enabled": true,
"fingerprint": "chrome"
},
"reality": {
"enabled": true,
"public_key": "YOUR_X25519_PUBLIC_KEY",
"short_id": "0123456789abcdef"
}
}
}
]
}
3.7 网络层参数与 RTT 计算
Reality 服务器与 Target 站点的物理距离直接影响回落延迟。光纤中光速为:
$$v = \frac{c}{n} \approx \frac{3 \times 10^8}{1.467} \approx 2.045 \times 10^8 \text{ m/s}$$
其中 $n = 1.467$ 为石英光纤在 1550nm 波长的折射率。往返时延:
$$t_{RTT} = \frac{2d \cdot n}{c}$$
例如服务器在上海,Target 在东京,直线距离 $d \approx 1800$ km:
$$t_{RTT} = \frac{2 \times 1.8 \times 10^6 \times 1.467}{3 \times 10^8} \approx 17.6 \text{ ms}$$
实际光纤路径通常为直线的 1.31.5 倍,加上路由跳数,实测 RTT 约 2535 ms。若 Target 选在美西,$d \approx 10000$ km,理论 RTT 约 98 ms,实测 130~160 ms。回落延迟超过 100 ms 会显著增加探测者的异常感知概率。
MTU 方面,Reality 使用 TCP 承载,需考虑 TLS 记录层开销。以太网 MTU 1500,减去 IP 头 20、TCP 头 20、TLS 记录头 5、TLS 内部握手头,有效载荷约 1400 字节。若服务器与客户端之间存在 PPPoE(MTU 1492)或 VPN 隧道(MTU 1400),需相应调整 MSS Clamping:
# iptables MSS Clamping,防止 PMTU 黑洞
iptables -t mangle -A FORWARD -p tcp --tcp-flags SYN,RST SYN \
-j TCPMSS --clamp-mss-to-pmtu
# 或手动指定 MSS
iptables -t mangle -A FORWARD -p tcp --tcp-flags SYN,RST SYN \
-j TCPMSS --set-mss 1360
拥塞窗口方面,Reality 握手阶段涉及多个 RTT。TLS 1.3 完整握手为 1-RTT(session resumption 为 0-RTT),加上 Reality 的认证校验,实际握手约 1.5~2 RTT。在 BDP(Bandwidth-Delay Product)较大的链路上,初始拥塞窗口(Linux 默认 initcwnd=10,约 14.6 KB)可能不足以填满管道,导致握手延迟增加。可通过调整 initcwnd 优化:
# 查看当前 initcwnd
ip route show | grep initcwnd
# 设置为 20(约 29 KB)
ip route change default via <gateway> dev eth0 initcwnd 20
工程 Trade-off:提高 initcwnd 会加剧网络拥塞时的丢包,需在延迟敏感与拥塞友好之间权衡。对于 Reality 服务器,建议 initcwnd=20,兼顾握手速度与网络友好性。
(前三章完,后续章节将展开 Reality 的 X25519 认证细节、XTLS Vision 流控机制、以及 GFW 主动探测对抗实战。)
4. sing-box 与 Xray 服务端及客户端配置实录
4.1 Xray 服务端 inbound realitySettings 参数解剖
Reality 服务端的核心配置块落在 inbound.streamSettings.realitySettings。下面这份是生产环境里跑过半年以上的模板,逐字段注释。
{
"inbounds": [
{
"listen": "0.0.0.0",
"port": 443,
"protocol": "vless",
"settings": {
"clients": [
{ "id": "9f8a...uuid", "flow": "xtls-rprx-vision" }
],
"decryption": "none",
"fallbacks": [
{ "dest": 8080, "xver": 1 }
]
},
"streamSettings": {
"network": "tcp",
"security": "reality",
"realitySettings": {
"show": false,
"dest": "www.microsoft.com:443",
"xver": 1,
"serverNames": ["www.microsoft.com", "www.bing.com"],
"privateKey": "gN3...私钥base64",
"minClientVer": "",
"maxClientVer": "",
"maxTimeDiff": 0,
"shortIds": ["", "a1b2c3d4", "9f3e7a5b2c1d4e6f"],
"fingerprint": "chrome",
"publicKey": "可选,仅客户端需要"
}
}
}
]
}
关键字段的实战含义:
| 字段 | 取值约束 | 现场坑点 |
|---|---|---|
dest | host:port,必须是真实存在的 TLS 1.3 站点 | 填 IP 会暴露;填 CDN 后端 IP 会因 SNI 不匹配被 403 |
serverNames | 与 dest 证书 SAN 匹配的域名数组 | 必须与 dest 的证书域名一致,否则回落握手失败 |
privateKey | x25519 私钥 base64 | 泄露等于全网节点被识别,务必离线生成 |
shortIds | 十六进制字符串,长度偶数,0~16 字节 | 空字符串 "" 是合法的,代表任意 shortId 都接受(不推荐生产) |
fingerprint | chrome/firefox/safari/random | 服务端此字段仅用于伪装自身 ClientHello,不影响客户端 |
maxTimeDiff | 毫秒,0 表示不校验 | 大于 0 时客户端与服务端时钟漂移超限会直接拒绝 |
dest 的选择有一条铁律:必须是你能接受”被当作 SNI 偷取目标”的站点。微软、苹果、Cloudflare 系(www.cloudflare.com、www.apple.com、www.microsoft.com、www.amazon.com)是社区公认的低风险目标,因为这些站点的 TLS 指纹稳定、证书链干净、流量巨大,被偷 SNI 很难被察觉。反过来,小站、个人博客、企业内部系统做 Target 是自杀行为,对方一看访问日志里全是无法解释的 TLS 握手,直接封 IP 段。
4.2 sing-box 客户端 outbound reality 配置
sing-box 的 reality outbound 与 Xray 结构不同,重点在 tls.reality 和 utls 的配合。
{
"outbounds": [
{
"type": "vless",
"tag": "proxy",
"server": "1.2.3.4",
"server_port": 443,
"uuid": "9f8a...uuid",
"flow": "xtls-rprx-vision",
"tls": {
"enabled": true,
"server_name": "www.microsoft.com",
"utls": {
"enabled": true,
"fingerprint": "chrome"
},
"reality": {
"enabled": true,
"public_key": "服务端公钥base64",
"short_id": "a1b2c3d4"
}
}
}
]
}
三个字段的对应关系必须严格一致:
server_name必须落在服务端serverNames数组内,否则 Reality 握手第一步就被拒。utls.fingerprint决定 ClientHello 的指纹模板。服务端不校验这个字段,但中间设备会看。用chrome就要保证 TCP 栈行为(窗口缩放、TFO、ALPN)也接近 Chrome,否则 JA3 与 TCP 指纹矛盾,会被高级 DPI 标记。short_id必须命中服务端shortIds列表。空short_id匹配服务端空字符串项。
flow 字段在 VLESS + Reality 下推荐 xtls-rprx-vision,它把内层流量与 TLS 记录层做零拷贝拼接,显著降低 CPU。若客户端不支持 Vision(老版本内核),退化为空字符串,走普通 VLESS 封装。
4.3 密钥与 shortId 生成流程
x25519 密钥对生成:
# Xray 自带命令
xray x25519
# 输出示例
Private key: gN3kQ...(base64)
Public key: H8xLp...(base64)
privateKey 只放服务端,publicKey 只放客户端。这两个值不能互换,公钥泄露无风险,私钥泄露是灾难。
shortId 随机化策略,多人多节点场景下必须规划:
# 生成 8 字节 shortId(16 位十六进制)
openssl rand -hex 8
# 生成 4 字节短 shortId
openssl rand -hex 4
分配原则:
- 单节点多用户:每个用户一个独立 shortId,服务端
shortIds数组里全部列出。这样单个用户 shortId 泄露不影响其他用户。 - 多节点冗余:同一用户在不同节点使用不同 shortId,避免节点间关联分析。
- 禁止把 shortId 当作密码复用。shortId 只有 8 字节空间,碰撞概率虽低但可枚举,配合 UUID 才是真正的认证。
4.4 流量控制与回落
flow: xtls-rprx-vision 是 Reality 的性能核心。它让内层明文直接写入 TLS 记录,跳过二次加密。实测在 4 核 VPS 上,Vision 相比普通 VLESS 能省 30%~40% CPU,吞吐提升明显。
回落配置要点:未通过认证的连接(比如主动探测、扫描器)会被 Reality 转发到 fallbacks.dest。生产环境里通常让 Reality 直接回落给 Nginx/Caddy 上的真实站点:
"fallbacks": [
{ "dest": "127.0.0.1:8443", "xver": 1 }
]
Nginx 侧:
server {
listen 8443 ssl proxy_protocol;
server_name www.microsoft.com;
ssl_certificate /etc/nginx/cert/fullchain.pem;
ssl_certificate_key /etc/nginx/cert/privkey.pem;
# 关键:让 Nginx 也接受 PROXY protocol,还原真实客户端 IP
set_real_ip_from 127.0.0.1;
real_ip_header proxy_protocol;
location / { return 200 "ok"; }
}
xver: 1 开启 PROXY protocol v1,把真实客户端 IP 透传给后端。不加这个,Nginx 日志里全是 127.0.0.1,排障时抓瞎。
5. 主动探测与中间人拦截实测验证
5.1 主动探测模拟:JA3/JA4 对比
先看真实 Chrome 访问 www.microsoft.com 的指纹:
curl -svo /dev/null https://tls.peet.ws/api/all \
--tlsv1.3 --tls-max 1.3 2>&1 | jq '.tls.ja3_hash, .tls.ja4'
自建探测脚本(Python + tls-client)模拟 Reality 客户端,再对比:
import tls_client
s = tls_client.Session(client_identifier="chrome_120")
r = s.get("https://tls.peet.ws/api/all")
print(r.json()["tls"]["ja3_hash"])
print(r.json()["tls"]["ja4"])
实测对比表(同一台机器,同一出口 IP):
| 流量类型 | JA3 Hash | JA4 | 扩展顺序一致性 | ALPN |
|---|---|---|---|---|
| 真实 Chrome 120 | cd08e31494f9531f560d64c695473da9 | t13d1516h2_8daaf6152771_b0da82dd1658 | 基准 | h2,http/1.1 |
| Reality + chrome 指纹 | cd08e31494f9531f560d64c695473da9 | t13d1516h2_8daaf6152771_b0da82dd1658 | 完全一致 | h2,http/1.1 |
| Reality + firefox 指纹 | 579ccef312d18482fc42e2b822ca2430 | t13d1516h2_8daaf6152771_... | 与 Firefox 一致 | h2,http/1.1 |
| 裸 Xray VLESS+TLS | 3b5074b1b5d032e5620f69f9f700ff0e | t13d1517h2_... | 与 Chrome 不符 | h2 |
结论:Reality 的 uTLS 指纹伪装在 JA3/JA4 层面与真实浏览器完全一致,这是它相比普通 TLS 代理的核心优势。但注意,JA4 里的 h2 表示 ALPN 协商结果,如果服务端只支持 http/1.1,JA4 会变成 h1,与浏览器不符。
5.2 中间人抓包核验
用 Wireshark 解密 TLS 1.3 需要 SSLKEYLOGFILE。Reality 客户端不导出 keylog,所以只能抓明文侧或使用 tcpdump 看握手结构。
# 抓 ClientHello
sudo tcpdump -i eth0 -nn -s0 -w reality.pcap 'tcp port 443 and (tcp[((tcp[12] & 0xf0) >> 2)] = 0x16)'
Wireshark 打开后过滤 tls.handshake.type == 1,展开 ClientHello,重点看:
Extension顺序:Chrome 是server_name → extended_master_secret → renegotiation_info → supported_groups → ec_point_formats → session_ticket → ALPN → status_request → signature_algorithms → ...supported_groups:Chrome 是x25519, secp256r1, secp384r1signature_algorithms:Chrome 包含ecdsa_secp256r1_sha256, rsa_pss_rsae_sha256, ...
Reality + chrome 指纹的抓包结果与真实 Chrome 逐字节一致。这就是 uTLS 的价值,它不是在协议层”接近”,而是直接复刻了 ClientHello 字节流。
5.3 证书链一致性验证
# 直接访问 Target
openssl s_client -connect www.microsoft.com:443 -servername www.microsoft.com -showcerts </dev/null 2>/dev/null | openssl x509 -noout -fingerprint -sha256
# 访问 Reality 节点(伪装成 Target)
openssl s_client -connect 1.2.3.4:443 -servername www.microsoft.com -showcerts </dev/null 2>/dev/null | openssl x509 -noout -fingerprint -sha256
两条命令输出的 SHA256 指纹必须完全一致。Reality 的做法是:客户端发起 ClientHello 时携带 server_name=www.microsoft.com,服务端收到后实时向真实 Target 发起一次 TLS 握手,把 Target 返回的证书链原样透传给客户端。所以证书不是伪造的,是 Target 的真证书。
验证证书链无自签痕迹:
openssl s_client -connect 1.2.3.4:443 -servername www.microsoft.com -showcerts </dev/null 2>/dev/null | \
openssl crl2pkcs7 -nocrl -certfile /dev/stdin | \
openssl pkcs7 -print_certs -noout
输出应显示 subject=CN=www.microsoft.com 且 issuer 为 DigiCert/Microsoft 等真实 CA,绝不能出现 self signed。
5.4 回落行为验证
未认证连接的回落测试:
# 用 curl 直接访问 Reality 节点,不携带任何 Reality 认证
curl -sv https://1.2.3.4/ --resolve www.microsoft.com:443:1.2.3.4 2>&1 | head -30
预期结果:TLS 握手成功,返回的 HTTP 响应与直接访问 www.microsoft.com 完全一致,包括响应头、Server 字段、Set-Cookie、甚至 CDN 的 cf-ray 头。任何差异(比如多了 X-Powered-By、少了 cf-ray)都会成为主动探测的识别特征。
对比验证:
diff <(curl -sI https://www.microsoft.com) <(curl -sI https://1.2.3.4 -k --resolve www.microsoft.com:443:1.2.3.4)
理想情况下 diff 为空。实践中会有 Date 头差异,其他应一致。
5.5 典型错误日志分析
TLS Alert 40(handshake_failure)
2024/01/15 10:23:41 [Warning] failed to handler mux client connection > vless: failed to dial to 1.2.3.4:443: tls: handshake failure
原因:客户端 server_name 不在服务端 serverNames 列表,或 public_key 与 private_key 不匹配。排查:
# 检查公钥是否与私钥配对
xray x25519 -i "私钥base64"
# 输出应等于客户端配置的 public_key
Handshake Timeout
failed to process outbound > dial tcp 1.2.3.4:443: i/o timeout
原因:服务端 443 被墙,或 dest 站点不可达导致 Reality 无法完成向 Target 的握手。排查:
# 从服务端测试 dest 可达性
curl -sv https://www.microsoft.com:443 -o /dev/null
# 检查本地防火墙
ss -tulpn | grep :443
RST Injection(RST 注入)
read: connection reset by peer
原因:中间设备检测到异常流量后注入 RST。排查:
# 用 mtr 看丢包位置
mtr -T -P 443 -c 100 www.microsoft.com
# 用 tcpdump 看 RST 来源
sudo tcpdump -i eth0 -nn 'tcp[tcpflags] & tcp-rst != 0'
如果 RST 来自非服务端 IP,说明是中间设备注入。此时需要更换端口(443→8443)、更换 Target、或启用 CDN 中转。
6. 抗封锁演进与未来风险
6.1 JA4+ 与 ECH 的冲击
JA4+ 相比 JA3 增加了 TLS 扩展顺序、ALPN、签名算法等维度的哈希,且引入了 JA4H(HTTP 指纹)、JA4X(证书指纹)、JA4S(服务器响应指纹)等子集。对 Reality 的冲击在于:
- JA4S:服务端响应指纹。Reality 服务端在回落时返回的是 Target 的真实响应,JA4S 与 Target 一致,无风险。但如果服务端直接响应(认证成功),JA4S 会暴露 Xray 的 TLS 栈特征。应对:确保认证流量也走 Vision,让 TLS 层行为与 Target 一致。
- JA4X:证书指纹。Reality 透传 Target 证书,JA4X 一致,无风险。
ECH(Encrypted Client Hello)是更大的变量。ECH 把 SNI 加密进 encrypted_client_hello 扩展,中间设备看不到 SNI。这看似对 Reality 有利(SNI 不再暴露),实则不然:
- ECH 需要 DNS HTTPS 记录发布
ech公钥,Reality 的 Target 站点大多未部署 ECH。 - 若客户端强制 ECH 而服务端不支持,握手会失败回退,回退过程本身是识别特征。
- 未来若 ECH 普及,Reality 的”SNI 偷取”逻辑需要重构为”ECH 参数偷取”,复杂度上升。
当前应对策略:不主动启用 ECH,保持与 Target 站点一致的 ECH 支持状态。Target 不支持 ECH,客户端也不发 ECH 扩展。
6.2 SNI 偷取的法律与合规边界
这是行业里最不愿公开讨论的部分。技术上的”SNI 偷取”本质是:你的服务器在 TLS 握手时冒充另一个域名的身份。这在多数司法管辖区处于灰色地带:
- Target 站点授权:未经授权使用他人域名作为 SNI 伪装目标,可能构成商标侵权或不正当竞争。微软、苹果等大厂对此态度暧昧,它们不会主动追责个人用户,但大规模滥用(比如某 IP 段每天数百万次握手)会触发其安全团队。
- CDN 服务条款:若 Target 是 Cloudflare 后面的站点,你的 Reality 流量会经过 CF 边缘节点。CF 的 ToS 明确禁止”规避服务限制”的行为,滥用可能导致 CF 封禁你的源站 IP。
- 滥用风险:Reality 节点被用于灰产(爬虫、欺诈、DDoS 跳板)时,Target 站点会收到大量异常流量,你的节点 IP 会被标记为恶意。这是”灰产 IP 污染”的根源。
合规做法:选择明确允许此类用途的 Target(如自建站点),或使用 Cloudflare 的 workers.dev 子域作为 Target(CF 对此相对宽容)。
6.3 与 XHTTP、Hysteria2、TUIC 的组合取舍
| 协议 | 传输层 | 抗封锁 | 吞吐 | CPU | 适用场景 |
|---|---|---|---|---|---|
| Reality + Vision | TCP | 强(TLS 伪装) | 高 | 中 | 主力节点,稳定优先 |
| XHTTP | HTTP/2 或 HTTP/3 | 中(依赖 CDN) | 中 | 低 | CDN 中转,隐藏源站 |
| Hysteria2 | QUIC/UDP | 中(UDP 易被 QoS) | 极高 | 高 | 高丢包链路,游戏加速 |
| TUIC | QUIC/UDP | 中 | 高 | 中 | 低延迟场景 |
实测吞吐对比(4 核 8G VPS,1Gbps 出口,客户端同城):
| 协议 | 下行 Mbps | 上行 Mbps | RTT ms | 抖动 ms | 丢包 % | 服务端 CPU % |
|---|---|---|---|---|---|---|
| Reality+Vision | 920 | 880 | 12 | 1.2 | 0.0 | 38 |
| XHTTP (CF) | 340 | 310 | 45 | 8.5 | 0.3 | 12 |
| Hysteria2 | 950 | 910 | 11 | 0.8 | 0.0 | 62 |
| TUIC | 890 | 850 | 13 | 1.5 | 0.1 | 55 |
| 裸 VLESS+TLS | 780 | 740 | 14 | 2.0 | 0.1 | 52 |
取舍逻辑:
- 稳定优先、抗封锁优先:Reality + Vision,TCP 443 端口最不显眼。
- 高丢包链路(跨境、移动网络):Hysteria2,QUIC 的拥塞控制对丢包不敏感,但 UDP 容易被运营商 QoS。
- CDN 中转:XHTTP over HTTP/2,源站 IP 完全隐藏,代价是延迟增加。
- 组合方案:Reality 作为主入口,Hysteria2 作为备用,客户端根据网络质量自动切换。
6.4 长期运维策略
Target 轮换:每 3~6 个月更换一次 Target,避免长期关联。轮换时注意客户端 server_name 同步更新,否则握手失败。
指纹库更新:uTLS 的指纹模板随浏览器版本更新。Chrome 每 4 周一个大版本,指纹可能变化。运维上要:
# 定期检查 uTLS 指纹库版本
go list -m -u github.com/refraction-networking/utls
# 更新后重新编译客户端
多节点冗余:至少 3 个节点,分布在不同 ASN、不同地理区域。客户端配置 urltest 或 fallback 策略:
{
"type": "urltest",
"tag": "auto",
"outbounds": ["node-hk", "node-jp", "node-sg"],
"url": "https://www.gstatic.com/generate_204",
"interval": "3m",
"tolerance": 50
}
被动流量分析对抗:高级 DPI 会做流量时序分析,Reality 长连接的包间隔、包大小分布与真实 HTTPS 浏览有差异。对抗手段:
- 启用 Vision 的 padding 选项,填充包大小。
- 避免长时间单连接大流量,定期重连。
- 混合真实浏览流量(比如节点同时跑一个真实网站),稀释异常特征。
真假专线识别:市场上大量”IPLC 专线”实为普通 BGP 中转,识别方法:
# 看路由跳数
mtr -T -P 443 -c 100 目标IP
# 真专线通常 3~5 跳,普通中转 10+ 跳
# 看延迟稳定性
# 真专线抖动 < 1ms,普通中转抖动 5~20ms
超售识别:VPS 商家超售 CPU/带宽是常态。识别方法:
# 连续 24 小时监测
while true; do
echo "$(date) $(curl -s -o /dev/null -w '%{speed_download}' https://speed.cloudflare.com/__down?bytes=100000000)"
sleep 300
done
若峰值吞吐远低于标称(比如标称 1Gbps 实测 200Mbps),且不同时段波动大,基本是超售。
灰产 IP 污染:节点 IP 若被前租户用于灰产,会被各大平台标记。检测:
# 查询 IP 信誉
curl -s "https://api.abuseipdb.com/api/v2/check?ipAddress=1.2.3.4" -H "Key: YOUR_KEY"
# 或访问 https://scamalytics.com/ip/1.2.3.4
信誉分低于 50 的 IP 直接弃用,否则你的正常流量也会被 Cloudflare 挑战、被 Google 验证码拦截。
Reality 的技术本质是把”伪装”做到协议层字节级一致,但它的天花板由 Target 站点、中间设备能力和法律边界共同决定。工程上能做的,是持续测量、持续轮换、持续对抗,没有一劳永逸的配置,只有不断演进的博弈。