摘要:本文从网络分层与 OSI/TCP-IP 模型讲起,说明 Shadowsocks、VMess、VLESS、Trojan、Reality 各自工作在哪一层。在此基础上研究了代理协议从 Shadowsocks 到 VMess、Trojan 再到 Reality 的演进脉络,并基于 XTLS/REALITY 和 XTLS/Xray-core 的源码,梳理了 Reality 的核心机制。最后给出了一段升级后的 Go Demo,支持协议首包识别、完整 ClientHello 解析、SNI 动态路由,以及 JA3 指纹所需字段提取。代码仅用于技术研究。
前置知识:网络分层与代理协议的位置
要理解 Shadowsocks、VMess、Trojan、Reality 这些协议的区别,需要先搞清楚它们在网络栈中的位置。网络分层不是抽象概念,它直接决定了协议能伪装到什么程度、能被识别在哪里。
一、TCP/IP 四层模型与 OSI 七层模型
两种模型都是把网络通信按功能分层,只是切法不同。对代理协议来说,TCP/IP 四层更实用。
| TCP/IP 四层 | OSI 七层 | 代表协议/数据 | 作用一句话 | 代理协议例子 |
|---|---|---|---|---|
| 应用层 | 应用层、表示层、会话层 | HTTP、HTTPS、FTP、DNS、SOCKS5 | 解决”某个应用程序想发送什么数据、怎么解析” | Trojan 内层认证、VMess 请求头、SOCKS5 |
| 传输层 | 传输层 | TCP、UDP、端口 | 解决”端到端可靠传输,数据交给哪个进程” | Shadowsocks 直接跑在 TCP/UDP 上,Reality 在这里接管 TLS |
| 网络层 | 网络层 | IP、ICMP、路由 | 解决”数据包从哪来、到哪去,走哪条路” | 网络层本身不感知代理协议,但 IP 和 SNI 会被审查 |
| 链路层 | 数据链路层、物理层 | 以太网、Wi-Fi、光纤 | 解决”比特流在物理介质上怎么跑” | 和代理协议关系不大 |
一句话记忆:链路层传比特,网络层找路径,传输层管端口,应用层定语义。
二、代理协议分别工作在哪个层
这是理解 Shadowsocks、VMess、Trojan、Reality 差异的关键。
| 协议 | 主要工作层 | 说明 |
|---|---|---|
| SOCKS5 | 应用层 | 它只是一套”告诉代理服务器目标地址”的协议,本身不加密。很多代理工具在本地先开个 SOCKS5 服务,再由下层协议把流量传出去。 |
| Shadowsocks | 传输层之上/应用层之下 | 它把 SOCKS5 要转发的数据拿过来,用对称密钥加密后直接塞进 TCP/UDP 发走。它不伪装成 HTTP,也不做 TLS 握手,只是一条加密隧道。 |
| VMess | 应用层 | VMess 自己定义了请求头和数据格式,再交给 TCP、WebSocket、gRPC 等传输层承载。如果外层是 TLS+WebSocket,那 TLS 是另一层负责伪装的。 |
| VLESS | 应用层 | 和 VMess 类似,是 V2Ray/Xray 的代理协议,但头部更轻、职责更单一。通常配合 TLS/XTLS 作为外层。 |
| Trojan | 传输层(TLS) + 应用层 | 客户端和服务端先完成标准 TLS 握手(传输层),然后在 TLS 加密通道里用 Trojan 自己的密码认证(应用层)。它伪装成 HTTPS,但服务器需要自己的证书。 |
| Reality | 传输层(TLS 握手层) | Reality 不定义新的代理语义,它只解决”怎么让 TLS 握手看起来像访问真实网站”。真正的代理协议(如 VLESS)跑在 Reality 接管后的隧道里。 |
重点:Shadowsocks 和 VMess/VLESS 是”应用层代理协议”,它们负责”目标地址是什么”;Trojan 和 Reality 是”传输层伪装方案”,它们负责”让这条连接看起来像普通 HTTPS”。Reality 的创新在于:它连 TLS 证书和握手都懒得自己演,而是直接让真网站演。
三、几个必须理解的概念
1. SNI(Server Name Indication, 服务器名称指示)
一台服务器的一个 IP 上可能同时托管几百个网站。TLS 握手时,客户端需要在 ClientHello 里告诉服务器:”我要访问的是 www.microsoft.com“。这个字段就是 SNI。
- 没有 SNI,服务器不知道该给哪个证书。
- 审查者常常用 SNI 黑名单做初步过滤。
- Reality 的关键:让 SNI 指向真实网站(如
www.microsoft.com),并且真实证书也匹配这个 SNI。
2. TLS 握手与 ClientHello
TLS 握手是 HTTPS 建立安全连接的第一步。客户端先发 ClientHello,包含:
- TLS 版本
- 支持的密码套件(Cipher Suites)
- 支持的扩展(Extensions),包括 SNI、ALPN、SupportedVersions、KeyShare 等
- 一个 32 字节的随机数
ClientRandom - 在 TLS 1.3 中,
SessionId字段保留但不再用于会话恢复
服务器回应 ServerHello,然后发证书、密钥交换等。TLS 1.3 握手完成后,双方派生出会话密钥,后续通信加密。
Reality 的 trick 就是:在这个握手过程里,把客户端的 ClientHello 原样转发给真实目标,再把真实目标的 ServerHello/证书 透传回来。 从链路上看,就像客户端真的在和真实网站握手。
3. 证书与证书链
证书由 CA(证书颁发机构)签发,证明”这个公钥确实属于 www.microsoft.com“。浏览器/操作系统内置了可信 CA 列表,所以访问大网站时证书能验证通过。
传统代理服务器自己申请证书时,证书上的域名是代理服务器自己的域名,审查者可以把这个域名/指纹拉黑。Reality 的方案是:不申请证书,直接借用真实网站的证书。
4. 透明转发与接管
- 透明转发:收到客户端数据后,原样不改地发给另一个目标,再把目标返回的数据原样发回客户端。对双方来说,仿佛直连。
- 接管:在 TLS 握手的某个节点,代理服务器不再把真实目标的响应透传给客户端,而是自己生成后续 TLS 记录,用派生出的密钥和客户端建立隧道。
Reality 的认证分支就是:认证成功 → 接管;认证失败 → 透明转发给真实网站。
四、为什么分层视角很重要
如果你从分层角度看,就能明白为什么 Shadowsocks 容易被识别、为什么 Trojan 需要域名、为什么 Reality 是突破。
- Shadowsocks 在传输层直接发加密数据。审查者不需要解密,只看”这是一个非 HTTP/HTTPS 的 TCP 流”,就可能直接阻断。
- VMess 在应用层自己定义了一套格式。虽然外层可以套 TLS,但 VMess 请求头本身有固定长度和特征,DPI 可以学出来。
- Trojan 在传输层做得像 HTTPS。但因为它要扮演 HTTPS 服务器,所以审查者可以针对”这个 HTTPS 服务器”做指纹、证书、SNI 黑名单。
- Reality 在传输层不扮演 HTTPS 服务器,而是把自己变成真实 HTTPS 连接的一部分。审查者能看到的证书和 SNI 都是真网站的,无法从这一层识别出代理。
理解了这一层,再看后面的四协议演进,就会非常清晰。
代理协议的演进:为什么需要 Reality
第一代:Shadowsocks
Shadowsocks 是一个轻量级 SOCKS5 代理,核心设计是”把数据包加密成随机字节流”。
- 握手:几乎没有显式握手。客户端直接用预共享密钥(PSK)发加密数据。
- 加密:早期用流密码(AES-CTR、ChaCha20),后期升级到 AEAD(AES-GCM、ChaCha20-Poly1305)。
- 被识别的原因:虽然载荷不可读,但流密码时代的流量有统计特征;而且端口固定、首包结构有迹可循。升级到 AEAD 后好很多,但仍依赖固定密钥,没有”抗主动探测”机制——审查者可以直接用密钥尝试解密,或者通过时间/长度特征识别。
一句话总结:Shadowsocks 隐藏内容,但没有隐藏”这是一个代理连接”这件事。
第二代:VMess
VMess 是 V2Ray 的核心协议,设计目标是”特征更少、更灵活”。
- 握手:客户端发送一个加密的请求头,包含 UUID 派生的认证信息、目标地址、加密方式、传输层类型等。
- 认证:基于 UUID 和动态 ID(时间相关),服务端用正确 UUID 才能解密请求头。
- 加密:请求头和后续数据都用 AEAD 加密。
- 灵活:可以承载在 TCP、WebSocket、HTTP/2、gRPC 等多种传输层之上。
- 被识别的原因:VMess 的固定长度请求头、特定扩展使用方式,以及客户端和服务端实现中的一些指纹, eventually 可以被 DPI 学习。而且它的”伪装”依赖于上层传输(比如 TLS+WebSocket),TLS 层仍然需要自己的证书和域名。
一句话总结:VMess 把协议做得更复杂、更灵活,但本质上还是”自己造一个协议,然后努力伪装”。
第三代:Trojan
Trojan 是一个极简主义设计,它直接放弃”造协议”,而是说:** TLS 加密本来就是互联网常态,我们就在 TLS 里面跑代理。**
- 握手:客户端先和服务端完成标准 TLS 握手。服务端需要持有证书(通常是真实域名证书或自签证书)。
- 认证:TLS 握手成功后,客户端在加密通道里发送
hex(SHA224(password))+ SOCKS5 风格的目标地址请求。 - Fallback:如果认证失败,Trojan 服务端把流量转发给一个正常 HTTP 服务(比如本地 80 端口),让主动探测者看到一个正常网站。
- 被识别的原因:Trojan 比 VMess 更像 HTTPS,但它仍然需要自己的证书和域名。审查者可以记录证书指纹、SNI、TLS 指纹(JA3),建立”这个证书/指纹属于代理服务器”的黑名单。而且一旦证书是自签的或者域名不常见,就很容易被标记。
一句话总结:Trojan 把代理藏在 HTTPS 里,但”HTTPS 服务器”本身仍然是一个攻击面。
第四代:Reality
Reality 是 XTLS/Xray 项目 2023 年推出的方案。它直接否定了 Trojan 的 premise:为什么要自己当 HTTPS 服务器?为什么不能直接让真实的大网站当服务器?
- 核心思想:代理服务器自己不持有证书。客户端发起 TLS 握手时,代理服务器把
ClientHello原样转发给真实目标网站(如www.microsoft.com),把真实网站返回的ServerHello+ 证书链透传回客户端。 - 自己人认证:客户端和代理服务器通过 X25519 密钥交换、HKDF、AES-GCM,在
ClientHello的SessionId字段里藏一个加密的短 ID。服务端认证成功后”接管”握手;认证失败则把流量透明转发给真实网站。 - 突破点:链路上看到的证书、SNI、TLS 指纹全部来自真实网站,不需要自己的域名和证书,抗主动探测(失败走 fallback 到真实网站)。
一句话总结:Reality 不再伪装成 HTTPS,而是真的成为一次 HTTPS 连接的一部分,然后在关键时刻接管它。
四代对比
| 协议 | 伪装思路 | 认证方式 | 最大弱点 |
|---|---|---|---|
| Shadowsocks | 加密成随机流 | 预共享密钥 | 流量统计特征,无抗探测能力 |
| VMess | 加密请求头 + 多种传输层 | UUID + 动态 ID | 固定头部指纹,依赖 TLS 伪装 |
| Trojan | 标准 TLS + 内层认证 | SHA224(password) | 需要自己的证书/域名,可被拉黑 |
| Reality | 直接用真实网站证书 | X25519 + HKDF + AES-GCM | dest 网站被封则失效,长连接统计特征仍可能被 AI 识别 |
Reality 源码核心机制
我下载并阅读了 XTLS/Xray-core 的 transport/internet/reality/reality.go 和 github.com/xtls/reality 的核心文件(特别是 tls.go 中的 Server 函数)。下面是关键机制。
1. 客户端:在 SessionId 里藏暗号
Xray-core 的 transport/internet/reality/reality.go 中,UClient 负责构造客户端握手。关键代码逻辑如下:
1 | hello.SessionId = make([]byte, 32) |
SessionId 的 32 字节结构:
| 偏移 | 长度 | 内容 |
|---|---|---|
| 0-2 | 3 | Xray 版本号 X.Y.Z |
| 3 | 1 | 保留 0x00 |
| 4-7 | 4 | Unix 时间戳(大端) |
| 8-23 | 16 | shortId(客户端身份) |
| 24-31 | 8 | 其余空间(被加密后覆盖) |
前 16 字节被 AES-GCM 加密,整个 SessionId 看起来仍是随机字节,审查者看不出来。
2. 服务端:认证 + 分支
github.com/xtls/reality/tls.go 中的 Server 函数是核心。它一上来就做了三件事:
- 连接真实目标 dest 并发送 PROXY protocol 头(可选)。
- 用
MirrorConn包裹客户端连接,让从客户端读到的数据同时被转发给 dest。 - 读取 ClientHello 并尝试认证。
认证逻辑的关键代码:
1 | for _, keyShare := range hs.clientHello.keyShares { |
3. 分支行为
- 认证失败:
hs.c.conn != conn, 服务端直接io.Copy(target, underlying)和io.Copy(underlying, target), 把客户端和真实网站双向打通。审查者看到的是正常网站响应。 - 认证成功:
hs.c.conn = conn, 服务端继续调用hs.handshake()和hs.readClientFinished(), 自己伪造 TLS 1.3 握手记录,接管密钥,后续走代理隧道。
这就是 Reality 抗主动探测的核心:对陌生人,你就是一个正常网站的反向代理。
4. 服务端伪造握手记录
认证成功后,服务端不能直接把 dest 的响应原样发回客户端(那样客户端和 dest 会用自己的密钥完成握手,代理服务器就被架空了)。服务端会:
- 从 dest 读取
ServerHello、ChangeCipherSpec、EncryptedExtensions、Certificate、CertificateVerify、Finished等记录。 - 根据这些记录的结构,自己生成对应的 TLS 1.3 记录,但用自己的密钥。
- 让客户端以为它就是 dest 的响应,从而用代理服务器的密钥继续通信。
这部分代码在 handshake_server_tls13.go 里,与标准 Go crypto/tls 的服务器握手高度相似,但关键点被 Reality 接管。
升级 Demo 的目标
基于上面的源码理解,我把 Demo 升级到 v2,新增以下能力:
- 协议首包识别:判断 incoming 连接是 TLS、HTTP 还是未知协议。
- 完整 ClientHello 解析:版本、随机数、SessionID、密码套件、压缩方法、扩展列表、SNI、ALPN、SupportedVersions、KeyShare groups、SignatureSchemes。
- SNI 动态路由:根据解析出的 SNI 选择不同的真实目标网站。
- JA3 指纹字段提取:收集 JA3 计算所需的字段,但暂不引入外部依赖计算完整 hash。
- 保留 Reality 认证插槽:在代码中明确标注 X25519 + HKDF + AES-GCM 认证逻辑应插入的位置,但本 Demo 不实现(避免变成可部署翻墙工具)。
- 更详细的日志:方便观察不同客户端(OpenSSL、浏览器、curl)的握手特征差异。
完整代码
项目目录:
1 | reality-study/ |
main.go:
1 | package main |
运行与验证
1. 编译
1 | cd reality-study |
2. 运行(带 SNI 路由映射)
1 | ./reality-study.exe -listen 127.0.0.1:8444 -dest www.microsoft.com:443 -show -sni-map "www.microsoft.com:microsoft.com:443,www.apple.com:apple.com:443" |
启动日志:
1 | 2026/07/19 07:49:15 [+] 监听 127.0.0.1:8444, 默认目标 www.microsoft.com:443 |
3. 用 openssl 测试
1 | openssl s_client -connect 127.0.0.1:8444 -servername www.microsoft.com -showcerts </dev/null |
服务端日志:
1 | 2026/07/19 07:49:15 [127.0.0.1:9243] 协议识别: TLS |
openssl 输出关键行:
1 | Connecting to 127.0.0.1 |
注意:由于 SNI 映射把 www.microsoft.com 转发到了 microsoft.com:443,而 microsoft.com 实际指向 Azure CDN,所以证书 CN 是 *.azureedge.net,但证书链仍是微软 CA 签发。这正好说明 Demo 的 SNI 路由生效了。
4. Windows PowerShell 用户注意事项
博客前面的命令都是 bash 语法。在 Windows PowerShell 里运行,要注意三个问题。
4.1 端口被占用
如果看到:
1 | listen tcp 127.0.0.1:8444: bind: Only one usage of each socket address ... |
说明之前启动的 reality-study.exe 还在后台。先杀掉再启动:
1 | taskkill /F /IM reality-study.exe |
或者查找具体占用 8444 的进程:
1 | Get-Process -Id (Get-NetTCPConnection -LocalPort 8444).OwningProcess | Stop-Process -Force |
4.2 Windows 默认没有 openssl
PowerShell 里直接敲 openssl 会报 The term 'openssl' is not recognized。最常见的解决办法:
方案 A:用 Git Bash 自带的 openssl
Git for Windows 安装后,openssl.exe 通常在:
1 | C:\Program Files\Git\usr\bin\openssl.exe |
在 PowerShell 里可以用完整路径调用,并把它加到 PATH:
1 | $openssl = "C:\Program Files\Git\usr\bin\openssl.exe" |
"Q" |是为了让 openssl 在打印完证书后退出。PowerShell 不支持 bash 的</dev/null输入重定向。
方案 B:单独安装 OpenSSL
去 slproweb.com/products/Win32OpenSSL.html 下载 Win64 OpenSSL Light,安装时勾选加入 PATH,重启 PowerShell 后就能直接用 openssl。
方案 C:用 Git Bash 终端
直接打开 Git Bash,所有博客里的命令都能原样运行,包括 </dev/null。
4.3 PowerShell 不支持 < /dev/null
bash 写法:
1 | openssl s_client -connect 127.0.0.1:8444 -servername www.microsoft.com -showcerts </dev/null |
在 PowerShell 里会报错:
1 | The '<' operator is reserved for future use. |
改成管道:
1 | "Q" | openssl s_client -connect 127.0.0.1:8444 -servername www.microsoft.com -showcerts |
5. 用浏览器验证(最直观)
启动 Demo 后,直接用浏览器打开:
1 | https://127.0.0.1:8444 |
浏览器会提示证书不安全(因为访问的是 IP 而不是域名,和证书 CN 不匹配),点击”高级”查看证书,应该能看到:
- 颁发对象:
www.microsoft.com - 组织:
Microsoft Corporation - 颁发者:
Microsoft TLS G2 RSA CA OCSP 04之类的微软 CA
这就证明 Demo 已经把真实网站的证书透传到了你的浏览器。页面可能显示 Invalid URL 或 edgesuite 的错误,这是正常的——这个 Demo 只研究 TLS 层,不处理 HTTP 代理逻辑。
关键实现点解析
1. 协议首包识别
TLS 的 record 头固定为 5 字节:
| 字节 | 含义 |
|---|---|
| 0 | ContentType, Handshake = 0x16 |
| 1-2 | 记录版本 |
| 3-4 | 载荷长度 |
HTTP 请求则以方法名开头。Demo 据此把连接分为 TLS / HTTP / 未知三类。
2. ClientHello 的完整解析
在 parseClientHello 中,我们按 RFC 5246/8446 的结构顺序解析:
1 | Version(2) + Random(32) |
扩展部分再逐个解析:
0x0000server_name: 提取 SNI0x0010ALPN: 提取应用层协议0x002bsupported_versions: 提取 TLS 版本偏好0x0033key_share: 提取密钥交换 group ID0x000dsignature_algorithms: 提取签名算法
3. JA3 指纹字段
JA3 是一种 TLS 客户端指纹算法,它把 ClientHello 中的以下字段拼成一个字符串,然后 MD5:
- TLS Version
- Cipher Suites
- Extensions
- Supported Groups(椭圆曲线)
- EC Point Formats
我们的 Demo 已经收集了这些字段,如果需要,可以进一步用 crypto/md5 计算完整 JA3 字符串。但这里为了代码简洁和避免外部依赖,只打印原始字段,便于手动观察指纹差异。
4. SNI 动态路由
通过 -sni-map 参数,可以指定:
1 | www.microsoft.com:microsoft.com:443,www.apple.com:apple.com:443 |
代码根据解析出的 SNI 选择目标地址。这对应 Reality 配置中的 serverNames 和 dest 映射。
5. Reality 认证插槽
代码中保留了一段注释,明确标示了 Reality 认证应发生的位置。这个插槽如果要实现,需要:
- 从
info.KeyShareGroups和原始 record 中取出 X25519 公钥 - 用服务端
privateKey做curve25519.X25519 - 用 HKDF 派生
authKey - 用 AES-GCM 解密
SessionID[:16] - 校验版本、时间、shortId
本 Demo 没有实现,保持技术研究边界。
局限与下一步
当前局限
- 未实现 Reality 认证和隧道接管:Demo 只能透明转发 TLS 握手,不能真正成为代理隧道。
- 未处理 TLS 1.3 的 0-RTT / 重协商:只处理最简单的情况。
- JA3 未计算完整 hash:只打印字段,可进一步用 MD5 计算。
- 非 TLS 协议只做透明转发:没有针对 Shadowsocks/VMess/Trojan 做解析或识别。
下一步可深入
- 实现 Reality 认证插槽:把 X25519 + HKDF + AES-GCM 逻辑填进去,做成本地验证 demo。
- 接管 TLS 1.3 握手:参考
github.com/xtls/reality/handshake_server_tls13.go,自己生成 ServerHello、EncryptedExtensions、Certificate 等记录。 - 引入 uTLS:让客户端模拟真实浏览器指纹,而不是用 OpenSSL 的默认指纹。
- 做协议识别器:对 Shadowsocks/VMess/Trojan 的首包做启发式识别(注意:只能识别”疑似”,不能精确解密)。
法律声明
本文及代码仅供网络协议、TLS 握手和代理协议演进的技术研究。理解 Shadowsocks、VMess、Trojan、Reality 的设计原理,有助于学习网络安全、DPI 与反检测技术,本身是中性的知识。
但将这些技术用于搭建或运营可规避网络审查的代理服务,在中国大陆可能违反《网络安全法》《计算机信息网络国际联网管理暂行规定》等法规。技术研究归技术研究,实际部署的法律边界需要自己把握。
参考资料
- XTLS/Xray-core:
https://github.com/XTLS/Xray-core - XTLS/REALITY:
https://github.com/xtls/reality - Trojan 协议规范:
https://trojan-gfw.github.io/trojan/protocol - VMess 协议 DeepWiki:
https://deepwiki.com/v2fly/v2ray-core/3.1-vmess-protocol - Shadowsocks AEAD 协议:
https://shadowsocks.org/guide/aead