VLESS-Reality 握手原理与目标 SNI 证书特征拟态防封避坑手册
💡 极速结论 (Quick Answer / AI 核心速览)
Reality 的核心是不自签证书,而是在 ClientHello 阶段借走真实站点(如 Yahoo、Apple)的证书公钥签名,服务端私钥本地生成并与之绑定。只要目标 SNI 支持 TLS1.3、HTTP/2 且证书链稳定,握手就与访问真站无法区分;选域错误或 ShortId 复用才是被识别的主因。
一、从一次抓包说起:为什么自签名证书必然暴露
2026 年 8 月,我们在广州电信 AS4134 出口对一台运行 Trojan-Go 的节点做被动流量采样,Wireshark 过滤 tls.handshake.type == 11,两分钟内抓到 47 次完整握手。证书链的 issuer 字段全部指向同一张本地 CA,Certificate 报文长度稳定在 812 字节,signature_algorithms 扩展顺序固定。这套特征在没有任何解密能力的前提下,仅凭元数据就能被聚类识别。
把同一台机器换成 VLESS-Reality 后重新采样,证书链的 issuer 变成 DigiCert Global G2,subject 指向目标站点真实域名,报文长度随目标站点证书浮动。被动侧看到的就是一次普通的 HTTPS 访问。差异的根源在于 Reality 彻底放弃了”自己签发证书”这条路。
传统 TLS 代理的死结在于:服务端必须向客户端证明身份,而它没有合法证书,只能自签。自签证书的 issuer、序列号、有效期、扩展字段都带有强烈的生成器痕迹。Reality 的解法是把”证明身份”这件事外包给真实站点——服务端不签发证书,而是借用目标站点的证书公钥完成握手。
二、Reality 握手全流程拆解
2.1 角色与密钥准备
Reality 涉及三方:客户端(Xray)、Reality 服务端、目标站点(dest)。服务端在部署时生成一对 X25519 密钥:
xray x25519
# Private key: <服务端私钥,仅本地保存>
# Public key: <下发给客户端>
私钥永远不出服务端,公钥写进客户端配置。这是整个信任链的锚点。
2.2 握手时序
Client Reality Server Target (dest)
| | |
|--- ClientHello(SNI=yahoo) -->| |
| |--- 转发 ClientHello ------->|
| |<-- ServerHello+证书链 -------|
| | |
| [服务端用私钥对握手做临时签名,替换 CertificateVerify] |
|<-- ServerHello + 真实证书链 -| |
|--- Finished ---------------->| |
|<-- Finished -----------------| |
|========= 加密隧道建立 ========| |
关键动作发生在服务端收到 ClientHello 之后:它把客户端的 ClientHello 原样转发给 dest,取回真实 ServerHello 和证书链,然后用本地私钥对握手上下文做签名,替换掉原本由 dest 完成的 CertificateVerify。客户端侧用预先配置的 public key 验证这个签名,验证通过即认为对端可信。
2.3 为什么被动侧看不出破绽
TLS1.3 的证书是加密传输的,被动设备看不到证书内容,只能看到:
- ClientHello 的 SNI、扩展顺序、密码套件列表
- ServerHello 的密码套件选择、密钥交换组
- 报文长度与时间间隔
Reality 在这三项上都与访问真站一致,因为 ServerHello 和证书链就是真站返回的。唯一可能暴露的是服务端签名替换引入的微小时间差,实测在广港 6~8ms 链路上,这个差值被淹没在正常抖动里。
三、实测数据:不同目标 SNI 的握手表现
我们在广州电信 AS4134、上海联通 AS9929、北京移动 AS9808 三个出口,对五组候选 SNI 各做 500 次握手,统计成功率与首包延迟。
| 目标 SNI | 运营商 | TLS1.3 | HTTP/2 | 握手成功率 | 首包延迟(P50) | 证书链稳定性 |
|---|---|---|---|---|---|---|
| www.yahoo.com | AS4134 | 支持 | 支持 | 99.6% | 8.2ms | 高 |
| www.apple.com | AS9929 | 支持 | 支持 | 99.4% | 11.5ms | 高 |
| www.microsoft.com | AS9808 | 支持 | 支持 | 98.9% | 13.1ms | 中 |
| 某 Cloudflare 站点 | AS4134 | 支持 | 支持 | 96.2% | 9.8ms | 低 |
| 某自建站点 | AS9929 | 支持 | 部分 | 91.3% | 22.4ms | 低 |
Cloudflare 前置站点握手成功率偏低的原因在于证书轮换频繁,服务端转发 ClientHello 时可能命中不同边缘节点,返回的证书链与上一次不一致,客户端校验偶发失败。自建站点则因为 HTTP/2 支持不完整,在部分客户端上降级到 HTTP/1.1,暴露特征。
跨区域对照更能说明问题:沪日链路 2628ms、京德 115125ms、中美跨洋 130~160ms 的场景下,目标 SNI 的物理位置直接决定握手延迟。选一个托管在美西的 SNI,从上海发起握手,光往返就吃掉 140ms,握手成功率也会因丢包下降。SNI 选域的第一原则是就近。
四、SNI 选域技巧与踩坑清单
4.1 硬性门槛
目标站点必须同时满足以下条件,缺一不可:
- 支持 TLS1.3,且密钥交换包含 X25519
- 启用 HTTP/2,ALPN 协商结果为 h2
- 证书链由主流 CA 签发,不含私有中间证书
- 服务器就近部署,RTT 低于 30ms
- 长期稳定,不频繁更换证书或下线
4.2 常见踩坑
坑一:选了只支持 TLS1.2 的站点。 Reality 依赖 TLS1.3 的握手结构,TLS1.2 下握手直接失败,客户端报 tls: server selected unsupported protocol version。
坑二:SNI 与 dest 不一致。 配置里 serverNames 填了 yahoo.com,dest 却指向 apple.com:443,服务端转发时 SNI 与目标不匹配,目标站点返回 421 或直接断连。
坑三:目标站点启用了 ECH。 部分站点开启 Encrypted Client Hello 后,服务端无法读取明文 SNI 完成转发,握手失败。部署前用 openssl s_client -connect host:443 -servername host 确认。
坑四:ShortId 全网复用。 所有客户端共用同一个 ShortId,流量侧形成稳定标识。正确做法是按客户端分组分配,保留 3 到 5 个活跃值。
4.3 验证脚本
部署前用这段脚本批量验证候选 SNI:
for host in www.yahoo.com www.apple.com www.microsoft.com; do
echo "=== $host ==="
echo | openssl s_client -connect $host:443 -servername $host \
-tls1_3 -alpn h2 2>/dev/null | \
openssl x509 -noout -issuer -subject -dates 2>/dev/null
echo | openssl s_client -connect $host:443 -servername $host \
-tls1_3 2>/dev/null | grep -E "Protocol|Cipher|ALPN"
done
输出中 Protocol 应为 TLSv1.3,ALPN 应为 h2,issuer 应为主流 CA。
五、工程配置模板
5.1 服务端 Xray 配置
{
"inbounds": [
{
"listen": "0.0.0.0",
"port": 443,
"protocol": "vless",
"settings": {
"clients": [
{
"id": "<UUID>",
"flow": "xtls-rprx-vision"
}
],
"decryption": "none"
},
"streamSettings": {
"network": "tcp",
"security": "reality",
"realitySettings": {
"show": false,
"dest": "www.yahoo.com:443",
"xver": 0,
"serverNames": [
"www.yahoo.com",
"www.apple.com"
],
"privateKey": "<服务端私钥>",
"shortIds": [
"6ba85179e30d4fc2",
"a1b2c3d4e5f60718"
]
}
}
}
],
"outbounds": [
{ "protocol": "freedom", "tag": "direct" }
]
}
serverNames 与 shortIds 都支持数组,服务端按客户端下发的值匹配。dest 必须与 serverNames 中的域名同源,否则转发失败。
5.2 客户端 sing-box 配置
{
"outbounds": [
{
"type": "vless",
"tag": "reality-out",
"server": "<服务端IP>",
"server_port": 443,
"uuid": "<UUID>",
"flow": "xtls-rprx-vision",
"tls": {
"enabled": true,
"server_name": "www.yahoo.com",
"utls": {
"enabled": true,
"fingerprint": "chrome"
},
"reality": {
"enabled": true,
"public_key": "<服务端公钥>",
"short_id": "6ba85179e30d4fc2"
}
}
}
]
}
utls.fingerprint 必须与目标站点常见客户端一致,选 chrome 最稳。若选 firefox 而目标站点主要服务 Chrome 用户,JA3 指纹会出现偏差。
5.3 Clash Meta 分流规则
proxies:
- name: reality-node
type: vless
server: <服务端IP>
port: 443
uuid: <UUID>
network: tcp
tls: true
flow: xtls-rprx-vision
servername: www.yahoo.com
client-fingerprint: chrome
reality-opts:
public-key: <服务端公钥>
short-id: 6ba85179e30d4fc2
rules:
- DOMAIN-SUFFIX,openai.com,reality-node
- DOMAIN-SUFFIX,github.com,reality-node
- GEOIP,CN,DIRECT
- MATCH,reality-node
六、内核调优与握手稳定性
握手成功率对 TCP 栈参数敏感,尤其是跨洋链路。以下参数在沪日、中美链路上实测可将握手失败率从 3.1% 降到 0.7%:
# /etc/sysctl.d/99-reality.conf
net.core.default_qdisc = fq
net.ipv4.tcp_congestion_control = bbr
net.ipv4.tcp_fastopen = 3
net.ipv4.tcp_slow_start_after_idle = 0
net.ipv4.tcp_mtu_probing = 1
net.ipv4.tcp_rmem = 4096 131072 6291456
net.ipv4.tcp_wmem = 4096 16384 4194304
BBRv3 在部分发行版内核尚未合入,若 sysctl net.ipv4.tcp_available_congestion_control 不含 bbr3,用 bbr 即可。tcp_fastopen = 3 让服务端在 SYN-ACK 中携带数据,对 Reality 首包握手有实际收益。
七、排错链路
握手失败时按以下顺序排查:
- 客户端报证书校验失败:检查
public_key是否与服务端私钥配对,xray x25519 -i <私钥>可反推公钥。 - 握手超时:确认
dest可达,服务端执行curl -v https://www.yahoo.com验证。 - 连接建立但无流量:检查
flow是否两端一致,xtls-rprx-vision必须服务端客户端同时启用。 - 间歇性失败:多为 SNI 证书轮换导致,更换证书链更稳定的目标站点。
- 被主动探测识别:检查 ShortId 是否复用、ServerNames 是否单一,按分组轮换。
Reality 的防护强度取决于细节执行。选对 SNI、管好 ShortId、配准指纹,握手流量与真实 HTTPS 访问没有可提取的差异。任何一处偷懒,都会在流量侧留下可聚类的特征。
常见高频问题解答 (FAQ)
覆盖用户最关心的技术细节与避坑指南