Trojan 协议
位置:核心协议层 | 难度:中等 | 前置:TLS 1.3 深入、TLS-in-TLS源码证据:Trojan 官方协议规范 (trojan-gfw.github.io)、
proxy/trojan/protocol.go、proxy/trojan/server.go外部参考:Trojan Protocol Spec、nthLink White Paper、Trojan-Killer POC
1. 协议定位
Trojan 由 GreaterFire 于 2017 年创建,是最早采用"完全伪装为 HTTPS"思路的代理协议。它的核心假设是:如果代理流量在外观上与普通 HTTPS 无法区分,审查方就无法在不产生巨大误伤的情况下进行阻断。
这个假设在 2017-2021 年间基本成立。但 2022 年 10 月的 GFW 大规模阻断事件和 2023 年 5 月的 Trojan-Killer 概念验证证明:TLS-in-TLS 嵌套是可检测的,仅靠外层 TLS 伪装不够。
2. 协议格式
Trojan 协议极其简单。整个协议规范(trojan-gfw.github.io)可以用一张图说明:
TLS 隧道建立后,客户端发送的首个数据包:
+-----------------------+---------+----------------+---------+----------+
| hex(SHA224(password)) | CRLF | Trojan Request | CRLF | Payload |
+-----------------------+---------+----------------+---------+----------+
| 56 | X'0D0A' | Variable | X'0D0A' | Variable |
+-----------------------+---------+----------------+---------+----------+
Trojan Request = SOCKS5-like 请求:
+-----+------+----------+----------+
| CMD | ATYP | DST.ADDR | DST.PORT |
+-----+------+----------+----------+
| 1 | 1 | Variable | 2 |
+-----+------+----------+----------+
CMD:
X'01' = CONNECT (TCP)
X'03' = UDP ASSOCIATE
ATYP:
X'01' = IPv4
X'03' = 域名
X'04' = IPv6第一个数据包的完整构成:
- 56 字节 =
hex(SHA224(password)):密码的 SHA224 哈希,以十六进制字符串形式发送 - 2 字节 = CRLF (
\r\n) - 变长 = Trojan Request:目标地址和端口
- 2 字节 = CRLF
- 变长 = 可选的数据载荷(允许首个数据包就携带实际数据)
2.1 密码哈希格式
hex(SHA224("password_string")) 的结果是 56 个十六进制字符(28 字节的 SHA224 哈希 → 56 字符的十六进制表示)。
例如 SHA224("mysecret") → hex = "a1b2c3d4..." 共 56 个字符。
这个固定 56 字节的 hex 字符串有一个值得注意的特征:全部 56 字节都在 ASCII hex 范围内([0x30-0x39, 0x61-0x66]),是连续的可打印 ASCII。这意味着:
- GFW Algorithm 1 Ex2:前 6 字节全是 ASCII → 豁免 ✓
- GFW Algorithm 1 Ex4:56 字节连续 ASCII → 豁免 ✓
这也是 Trojan 的设计意图:让首个数据包尽可能通过 GFW 的豁免规则。
2.2 服务端验证流程
源码位置:proxy/trojan/server.go + proxy/trojan/protocol.go
1. TLS 握手完成
2. 读取客户端首个数据包
3. 提取前 56 字节 → 与已注册用户的 hex(SHA224(password)) 比对
4. 匹配 → 解析后续的 CRLF + Trojan Request + CRLF + Payload
5. 不匹配 → 将连接转发到本地 Web 服务(回落)3. 回落机制
Trojan 的回落与 VLESS+REALITY 的回落有本质区别。
3.1 Trojan 的回落
非 Trojan 连接 → 转发到本地 Nginx/Apache
- 本地 Web 服务器返回真实网页
- 审查方进行主动探测 → 收到 Nginx 的响应 → 看起来是正常网站问题:如果服务端没有运行本地 Web 服务器(或 Web 服务器返回的是默认页面/错误页面),主动探测会立即发现异常。
3.2 REALITY 的回落(对比)
非 REALITY 连接 → 转发到任意远程网站 (如 microsoft.com)
- 审查方收到的是目标网站的真实 TLS 证书和页面
- 即使是空服务器,也能做到"看起来像 microsoft.com 的镜像"这是 REALITY 相对于 Trojan 的核心改进——回落不依赖本地环境。
4. GFW 对 Trojan 的检测演进
4.1 2022 年 10 月阻断
2022 年 10 月 3 日前后,超过 100 名反审查社区用户报告 Trojan-go、VLESS+TLS+WebSocket、VMess+TLS 等 TLS 隧道协议被大规模阻断。这是 GFW 首次展示出对 TLS-in-TLS 的检测能力。
4.2 Trojan-Killer(2023 年 5 月)
Xray 的创建者 RPRX 发布了一个名为 "Trojan-Killer" 的概念验证程序,证明了 TLS-in-TLS 检测不需要巨大的计算资源。Trojan-Killer 通过分析 TLS 握手阶段的数据包大小来精确定位 Trojan-go 服务端。
检测原理:当 Trojan 服务端将非 Trojan 连接转发到本地 Web 服务器时,Web 服务器返回的 HTTP 响应与 Trojan 转发的代理数据有不同的包大小特征。Trojan-Killer 利用 TLS 握手中ServerHello 后首个应用数据包的大小来进行区分。
4.3 2025 年 8 月 GFW 升级
据社区估算,2025 年 8 月 GFW 升级后,Trojan 的检测率上升到约 90%。主要利用的特征:
- TLS-in-TLS 数据包大小分布异常:外层 TLS 记录中的数据部分 = 内层 TLS + Trojan 请求头,长度偏大
- TLS 握手完成后首个数据包的固定长度模式:Trojan 的密码哈希 56B + CRLF 2B + 地址 ≈ 70-90B,这是一个集中的长度区间
- 流量的时序模式:Trojan 连接在 TLS 握手完成后立即推送数据(代理流量),而正常 HTTPS 在 TLS 握手后通常先等待客户端 HTTP 请求
5. 与 VLESS+REALITY 的详细对比
| 维度 | Trojan | VLESS + REALITY |
|---|---|---|
| 认证方式 | SHA224(password) 的 hex 字符串,明文发送在 TLS 隧道内 | ECDH + HKDF + HMAC,认证信息嵌入 TLS ClientHello 的 Session ID |
| 证书 | 需要真实 CA 签发的证书(如 Let's Encrypt) | 临时自签 Ed25519 证书,无需 CA |
| 回落目标 | 本地 Web 服务器 | 任意远程网站 |
| TLS-in-TLS 处理 | 无特殊处理 | XTLS Vision 分流,消除嵌套特征 |
| 服务端 TLS 指纹 | 可被 JA3/JA4 指纹识别(Nginx/Apache 指纹) | 无服务端指纹(使用目标网站的 TLS 栈) |
| domain 依赖 | 需要自己的域名 + CA 证书 | 不需要自己的域名(借用目标网站的 SNI) |
| 部署复杂度 | 需要配置域名、CA 证书、Web 服务器 | 只需配置目标网站地址和密钥对 |
| 当前检测率(估算) | ~90% | < 5% |
6. 密码哈希的已知弱点
6.1 固定长度特征
56 字节是一个固定值。GFW 可以通过统计学习识别"TLS 握手后首个应用数据包长度为 60-100 字节且前 56 字节熵偏低(全是 hex 字符)"的连接模式。
6.2 被动探测
Trojan 服务端需要先读取 56 字节才能判断连接是否为合法客户端。如果 GFW 发送一个小于 56 字节的探测包,服务端的行为取决于具体实现:
Go 版本实现(在 io.ReadFull 被阻塞时):
- TCP 的
read()可能返回少于 56 字节(TCP 是流协议) - 如果 Go 版本使用
io.ReadFull读取 56 字节,不足时会阻塞等待更多数据 - 这个等待行为本身是可被探测的
C 版本实现(trojan-gfw/trojan):
- 正确处理了 TCP 流的分段问题
SHA224 的 hex 输出范围完全在 [0-9a-f],熵远低于随机数据。这恰好是 GFW 豁免规则(Ex2-Ex4)会放过的东西——但也使 Trojan 的首包具有了另一个维度的可检测特征:56 字节的 hex 字符串 + CRLF + SOCKS5 请求的固定格式。
7. 源码结构
| 文件 | 核心内容 |
|---|---|
proxy/trojan/trojan.go | 协议注册、MemoryAccount 定义 |
proxy/trojan/config.go | 服务端/客户端配置 |
proxy/trojan/protocol.go | 协议编解码(密码验证、请求格式) |
proxy/trojan/server.go | 入站处理器:TLS 握手 → 密码验证 → 解析地址 → Dispatcher |
proxy/trojan/client.go | 出站处理器:TLS 连接 → 发送密码哈希 → 发送目标地址 → 中继 |
proxy/trojan/validator.go | 密码哈希验证 |
8. 外部参考资料
资料:The Trojan Protocol — 官方协议规范 类型:协议规范 链接:https://trojan-gfw.github.io/trojan/protocol.html 可信度:A
资料:Design discussion (Trojan Issue #14) 类型:项目设计讨论 链接:https://github.com/trojan-gfw/trojan/issues/14 可信度:A 建议参考:Trojan 设计者关于 TLS-in-TLS 检测的原始讨论
资料:nthLink White Paper 类型:技术白皮书 链接:https://www.opentech.fund/wp-content/uploads/2023/11/nthLink_whitepaper_are_shadowshocks_and_trojan-go_still_relevant.pdf 可信度:B 建议参考:Trojan-go 在 2022 年 GFW 升级后的阻断事件分析
读者自检
- Trojan 的首个数据包由哪些部分构成、每部分的字节数和格式
- 为什么 Trojan 的 56 字节 hex 密码哈希让首个数据包通过 GFW 的 ASCII 豁免规则
- Trojan 的回落与 REALITY 的回落有什么本质区别
- Trojan-Killer 如何通过 TLS 握手后首个应用数据包大小来识别 Trojan 服务端