AnyTLS 协议深度解析:从原理到实现

Play Apr 30, 2026

一、引言:AnyTLS 是什么?

AnyTLS 是一个旨在缓解 嵌套 TLS 握手指纹(TLS-in-TLS)问题 的新兴代理协议。它的名字 "AnyTLS" 传递了两个核心含义:

  1. Any in TLS — 任意流量都可以被封装在 TLS 连接中传输
  2. Any TLS — 理论上可以运行在任何 TLS 实现之上

该协议由社区开发者设计,其参考实现 anytls-go 以 Go 语言编写,目前已广泛集成到多个主流代理平台中,包括 sing-boxmihomo(Clash Meta)Shadowrocket 等。


二、背景:为什么要设计 AnyTLS?

2.1 TLS-in-TLS 指纹问题

传统的代理协议(如 Trojan、VLESS over TLS)通常会在客户端和目标服务器之间建立两层 TLS:

  • 外层 TLS:代理客户端 ↔ 代理服务器之间的加密隧道
  • 内层 TLS:代理服务器 ↔ 目标服务器之间的加密连接

然而,深度包检测(DPI)系统可以通过分析 嵌套 TLS 握手 的流量特征来识别代理行为。具体来说,当 DPI 系统观察到以下特征时,就可能判定这是一个代理隧道:

  1. 两次 TLS 握手发生在同一个 TCP 连接的后半段:正常的浏览器行为是 TLS 握手后就完成了,不会有第二次握手
  2. 数据包长度模式异常:TLS 握手产生的数据包大小具有特定的分布特征
  3. 数据包到达时间间隔异常:代理隧道的数据包时序与正常浏览行为不同
  4. TLS 头部和证书特征:特定的 TLS 栈实现(如 Go 的 TLS 库)会产生独特的 ClientHello 指纹

2.2 已有方案的局限性

方案 原理 局限性
uTLS 指纹伪装 修改 ClientHello 使其看起来像浏览器 只解决了握手包特征,未解决 TLS-in-TLS 的结构问题
XTLS-Vision 直接透传内层 TLS 流量,避免二次加密 写死的长度处理逻辑,一旦特征被识别就难以改变
流量混淆 随机填充或固定长度填充 缺乏策略性,容易产生新的可识别特征
WebSocket/CDN 中转 通过 WebSocket 或 CDN 隐藏代理流量 增加了延迟和复杂度,不适用于所有场景

AnyTLS 正是为了解决这些问题而设计的。它通过 灵活的分包和填充策略 以及 动态可更新的流量特征,从根本上提高了抗检测能力。


三、AnyTLS 协议架构

3.1 整体层次结构

AnyTLS 的协议栈分为四个层次:

┌──────────────────────────────────────────────┐
│              TCP Proxy (代理中继)              │
├──────────────────────────────────────────────┤
│         Stream (流) — 复用的数据通道          │
├──────────────────────────────────────────────┤
│      Session (会话) — 命令/事件循环           │
├──────────────────────────────────────────────┤
│        TLS (传输层安全性协议)                  │
├──────────────────────────────────────────────┤
│           TCP (传输控制协议)                   │
└──────────────────────────────────────────────┘
  • TLS 层:提供加密传输和身份验证,是整个协议的安全基础
  • Session 层:在 TLS 之上运行的事件循环,处理认证、命令分发和会话管理
  • Stream 层:在 Session 内复用的多个逻辑数据流,每个 Stream 对应一个代理请求
  • TCP Proxy 层:实际的中继逻辑,包括 SOCKS5 地址解析和数据转发

3.2 核心设计原则

  1. 连接复用:多个代理请求共享同一条 TLS 连接,减少握手开销
  2. 策略性填充:通过可配置的填充方案控制数据包长度分布
  3. 动态特征更新:允许服务器在运行时推送新的填充方案,使流量特征灵活可变
  4. 保持简单:不处理 TLS 指纹伪装(由外部工具实现),专注于解决核心问题

四、协议详述

4.1 TLS 握手

AnyTLS 协议本身不定义 TLS 握手的具体参数,而是将 TLS 层的配置交由上层应用决定。这意味着:

  • 可以使用自签名证书
  • 可以使用 Let's Encrypt 等 CA 签发的证书
  • 可以通过 uTLS 等库实现 ClientHello 指纹伪装
  • 可以配合 fallback 机制应对主动探测

这种设计使得 AnyTLS 具有良好的灵活性,可以根据部署环境自由选择 TLS 实现。

4.2 客户端认证

TLS 握手完成后,客户端会立即发送认证请求。认证包的格式如下:

sha256(password) padding0 length padding0
32 字节 2 字节(大端序 uint16) 可变长度

认证流程:

  1. 客户端计算密码的 SHA-256 哈希值(32 字节)
  2. 生成一个 2 字节的 padding0 长度值
  3. 发送 32 字节的密码哈希 + 2 字节长度 + 填充数据
  4. 服务器接收后验证密码哈希
    • 认证成功:进入会话层事件循环
    • 认证失败:关闭连接,或 fallback 到一个普通的 HTTP 服务(用于应对主动探测)

认证阶段的总开销仅为 34 字节(不含填充),非常轻量。

4.3 会话层(Session Layer)

认证完成后,客户端和服务器进入会话层事件循环。会话层的基本通信单元是 frame(帧),格式如下:

command streamId data length data
1 字节 4 字节(大端序 uint32) 2 字节(大端序 uint16) 可变长度

帧头开销约为 7 字节。

4.4 命令集

AnyTLS 协议目前有两个版本,共定义了 11 种命令:

版本 1(基础命令):

命令 编码 方向 说明
cmdWaste 0 双向 填充数据包,接收方将其完整读出后无声丢弃
cmdSYN 1 客户端→服务器 打开一条新的 Stream(数据通道)
cmdPSH 2 双向 推送数据,data 字段承载实际的传输数据
cmdFIN 3 双向 关闭指定 Stream(类似 TCP 的 FIN 标志)
cmdSettings 4 客户端→服务器 发送客户端设置(版本号、软件名称、填充方案 MD5)
cmdAlert 5 服务器→客户端 服务器发送警告信息,收到后双方关闭会话
cmdUpdatePaddingScheme 6 服务器→客户端 请求客户端更新填充方案

版本 2(增强命令):

命令 编码 方向 说明
cmdSYNACK 7 服务器→客户端 确认 Stream 已打开,可携带错误信息
cmdHeartRequest 8 双向 心跳探测请求
cmdHeartResponse 9 双向 心跳探测响应
cmdServerSettings 10 服务器→客户端 服务器发送设置信息

4.5 命令详解

cmdWaste(0)

这是填充策略的核心命令。任意一方收到 cmdWaste 后,都必须将其 data 完整读出并无声丢弃。这些数据通常用零填充,目的是调整数据包的长度分布,使其看起来更像正常的 TLS 流量。

cmdSYN(1)与 cmdSYNACK(7)

当客户端需要代理一个新的连接时,它会发送 cmdSYN 命令,并分配一个在 Session 内单调递增的 streamId。

  • 版本 1:服务器收到 cmdSYN 即开始代理,不返回确认
  • 版本 2:服务器在出站 TCP 连接握手完成后,发送 cmdSYNACK 回复。如果 cmdSYNACK 不带 data,表示代理成功;如果带 data,data 表示错误信息,客户端收到后必须关闭对应 Stream

这个改进解决了版本 1 中的一个重要问题:当隧道连接意外断开且客户端未收到 RST 时,可能会导致很长的超时。有了 cmdSYNACK,客户端可以在超时后主动关闭卡住的连接。

cmdPSH(2)

承载 Stream 的实际传输数据。这是数据量最大的命令类型。

cmdFIN(3)

关闭指定的 Stream。与 TCP 不同的是:

  • 正常关闭时,收到 cmdFIN 后不需要回复 cmdFIN
  • Session 关闭时不需要发送 cmdFIN

cmdSettings(4)与 cmdServerSettings(10)

客户端设置cmdSettings)格式:

v=2
client=anytls/0.0.1
padding-md5=(md5_of_padding_scheme)

采用 UTF-8 编码,key=value 格式,不同项目用 \n 分隔。

服务器设置cmdServerSettings)格式:

v=2

版本协商机制

  • v2 服务器 + v1 客户端:客户端发送版本 1,服务器不启用 v2 特性
  • v1 服务器 + v2 客户端:客户端发送版本 2,但服务器不认识,不会回复 cmdServerSettings。客户端未收到回复,默认版本为 1,不启用 v2 特性
  • v2 服务器 + v2 客户端:双方在 cmdSettings/cmdServerSettings 中确认支持 v2,启用全部特性

这种向后兼容的版本协商机制确保了不同版本的实现可以共存。

cmdAlert(5)

服务器发送警告文本,客户端打印到日志后双方关闭会话。用于拒绝不合规的客户端连接并说明原因。

cmdUpdatePaddingScheme(6)

这是 AnyTLS 最核心的创新之一。当服务器发现客户端使用的填充方案 MD5 与自身不同时,会发送此命令请求更新。

有了这个设计,当默认填充方案产生的流量特征被识别时:

  1. 只有第一个连接使用旧的、已知的填充方案
  2. 服务器立即下发新的填充方案
  3. 后续所有连接都使用新方案

这意味着理论上可以被识别的连接比例将极低,因为每个客户端只需要发送少量已知特征的数据,然后就能切换到服务器指定的安全特征。

cmdHeartRequest(8)与 cmdHeartResponse(9)

版本 2 的心跳机制,用于检测和恢复卡住的隧道连接。如果长时间未收到心跳响应,客户端可以主动关闭并重建连接。


五、填充方案(Padding Scheme)

填充方案是 AnyTLS 抗检测能力的核心。它通过一个可配置的策略来控制每个数据包的长度和行为。

5.1 语法格式

stop=8
0=30-30
1=100-400
2=400-500,c,500-1000,c,500-1000,c,500-1000,c,500-1000
3=9-9,500-1000
4=500-1000
5=500-1000
6=500-1000
7=500-1000

5.2 语法元素

元素 含义
stop=N 在第 N 个包之后停止处理填充(只处理包 0 到 N-1)
N=X-Y 第 N 个包的长度在 X 到 Y 字节范围内随机
c 检查符号:如果上一个分包发送完后用户数据已无剩余,则停止发送后续填充包
X-Y,Z-W 逗号分隔表示分包策略:将数据分成多个指定大小的包发送

5.3 填充策略的工作流程

  1. 包 0(padding0):处于认证阶段,不支持分包。客户端将指定长度的填充与密码哈希一起发送

  2. 包 1 开始:进入会话阶段,采用策略分包和/或填充

    • 如果分包发送完之后,用户数据仍有剩余,则直接发送剩余数据
    • 如果分包发送完之前,用户数据已发送完毕,则发送 cmdWaste 携带填充数据
  3. 包计数器:以 Write(TLS) 的次数为准

包 1 通常包含 cmdSettings 和首个 Stream 的 cmdSYN + cmdPSH(代理目标地址)
包 2 通常是代理自用户的第一个数据包,比如 TLS ClientHello。

5.4 示例:模拟 XTLS-Vision

stop=3
0=900-1400
1=900-1400
2=900-1400

这个方案可以模拟 XTLS-Vision 的流量特征。但 AnyTLS 的作者指出,XTLS-Vision 的弊端在于写死的长度处理逻辑——一旦 GFW 更新特征库就能识别。而 AnyTLS 的动态更新机制使得特征可以随时改变。

5.5 动态更新流程

客户端                       服务器
  │                           │
  │─── sha256(pwd) ──────────→│  TLS握手 + 认证(使用默认padding)
  │                           │
  │─── cmdSettings ──────────→│  发送填充方案MD5
  │     padding-md5=XXXX      │
  │                           │  ── 比对MD5,发现客户端方案已过时
  │←── cmdUpdatePaddingScheme─│  下发新的填充方案
  │     stop=8, 0=30-30,...   │
  │                           │
  │  后续所有连接使用新方案     │

六、连接复用(Multiplexing)

AnyTLS 要求客户端必须实现会话层复用功能

6.1 复用策略

  1. 创建新 Stream 之前:检查是否有"空闲"的 Session

    • 如果有:取 Seq 最大的 Session(即最近使用的),在该 Session 上开启新的 Stream
    • 如果没有:创建新的 Session,Seq 单调递增
  2. Stream 关闭时:如果对应 Session 的事件循环未遇到错误,将 Session 放入"空闲会话池",记录空闲起始时间

  3. 定期清理(如每 30 秒检查一次):

    • 关闭并删除持续空闲超过一定时间(如 60 秒)的 Session
    • 至少保留前 N 个空闲 Session 不关闭(为后续代理保留"预备会话")

6.2 复用策略的考量

复用策略高度概括为:优先复用最新的会话,优先清理最老的会话。这样做的好处是:

  • 最新的会话最可能保持活跃,复用它可以减少资源浪费
  • 清理最旧的会话可以释放资源
  • 保留一定数量的预备会话可以减少新建连接的开销

七、代理中继(Proxy)

7.1 TCP 代理

每个 Stream 打开后,客户端向服务器发送 SocksAddr 格式(RFC 1928 §5)的地址信息,表示代理请求的目标地址,然后开始双向代理中继。

7.2 UDP 代理

AnyTLS 本身不直接支持 UDP 代理。对于 UDP 流量,它使用 sing-box 的 udp-over-tcp 协议版本 2:将 UDP 数据封装在 TCP Stream 中传输,代理目标地址为虚拟的 sp.v2.udp-over-tcp.arpa


八、URI 格式

AnyTLS 定义了简洁的 URI 格式(参考了 Hysteria2 的 URI 方案):

anytls://[auth@]hostname[:port]/?[key=value]&[key=value]...

组件

组件 说明
协议名 anytls
认证 密码放在 URI 的 auth 部分(相当于标准 URI 的 username),特殊字符需百分号编码
地址 服务器地址和可选端口,默认端口 443
sni TLS SNI(Server Name Indication),值为 IP 地址时客户端不发送 SNI
insecure 是否允许不安全的 TLS 连接(1=允许,0=不允许)

示例

anytls://letmein@example.com/?sni=real.example.com
anytls://letmein@example.com/?sni=127.0.0.1&insecure=1
anytls://0fdf77d7-d4ba-455e-9ed9-a98dd6d5489a@[2409:8a71:6a00:1953::615]:8964/?insecure=1

九、已知弱点

AnyTLS 的作者坦诚地列出了协议的已知弱点,这些弱点目前可能还不会轻易导致协议被封锁,但值得关注:

  1. TLS-over-TLS 的握手往返次数:AnyTLS 比普通 HTTP/2 请求需要更多次的 TLS 握手。没有 MITM 代理的情况下难以避免这种情况

  2. 下行流量未处理:当前版本只处理上行(客户端→服务器)的流量特征,不处理下行。虽然修复这个问题不会破坏兼容性,但会影响性能

  3. 填充方案语法有限:目前只支持"单一固定长度"和"单一范围内随机"两种模式。剩余数据也只能直接发送。需要重新设计一套更复杂的语法

  4. 数据包发送时序:AnyTLS 几乎同时发送三个或更多数据包,尤其是在 TLS 握手后的第一个 RTT 内。即使单个包的长度符合要求,到达时间-包长-包数量到达时间-通信数据量 等统计特征仍可能被利用

  5. 包计数器不代表发包时机:无法预测被代理方的发送时机,因此包计数器不一定代表真正的发包时序

  6. TLS-over-TLS 开销:导致数据包长度增大和缺失小数据包,以及可能持续超过 MTU 限制

  7. 主动探测风险:虽然不是 HTTP 服务器,仍然存在被主动探测的风险(尽管 GFW 的主动探测行为已不多见)


十、实现与应用

10.1 参考实现

anytls-gohttps://github.com/anytls/anytls-go

  • 语言:Go
  • 状态:参考实现,简洁的示例,不旨在成为通用代理工具
  • 功能:提供服务器和客户端,支持 Socks5 代理

10.2 集成平台

平台 类型 说明
sing-box 通用代理平台 同时实现了服务器端和客户端,最完整的 AnyTLS 支持
mihomo(Clash Meta) 通用代理客户端 实现了 AnyTLS 客户端和服务器端
Shadowrocket iOS 代理客户端 2.2.65+ 版本实现了 AnyTLS 客户端
Throne 跨平台 GUI 基于 sing-box 的桌面代理工具,支持 AnyTLS

10.3 Rust 实现

anytls-rs 是一个 Rust 语言的 AnyTLS 实现,表明了该协议的跨语言兼容性。


十一、与其他代理协议的对比

协议 传输层 复用 填充策略 抗检测 特点
AnyTLS TLS ✅ 动态策略 ⭐⭐⭐⭐⭐ 灵活可变的流量特征
Trojan TLS ⭐⭐⭐ 简单,Go 实现 TLS 指纹明显
VLESS + XTLS-Vision TLS/XTLS ✅ 固定策略 ⭐⭐⭐⭐ 速度好,但填充策略固定
Hysteria2 QUIC ⭐⭐⭐ UDP 协议,速度优但易 QoS
NaiveProxy Chromium 栈 HTTP/2 天然填充 ⭐⭐⭐⭐ 使用浏览器内核 TLS 栈
Shadowsocks 自定义加密 ⭐⭐ 无 TLS 特征,但 AEAD 可识别

十二、总结与展望

AnyTLS 作为一个新兴的代理协议,其核心理念是 "可变的流量特征比不可识别的流量特征更有价值"。通过策略性的分包填充和动态更新机制,AnyTLS 从根本上改变了传统代理协议"静态防御"的思路。

AnyTLS 的设计哲学

  1. 关注可改变性,而非完美性:与其试图让流量变得完全不可识别,不如让它变得容易被改变
  2. 分层解耦:TLS 指纹伪装、证书管理、填充策略各司其职,互不干扰
  3. 简洁性:保持协议核心简单,复杂功能交由上层平台实现

未来方向

  • 下行流量处理:对称的流量特征控制
  • 更复杂的填充语法:支持更精细的数据包长度控制
  • 与 QUIC 的结合:AnyTLS over QUIC 的可能性
  • 更好的时序控制:减少数据包发送的"突发性"特征

AnyTLS 的出现代表了一种新的抗审查思路——与其与 DPI 系统进行"猫鼠游戏"式的零和博弈,不如让猫永远找不到同一只老鼠。这正是该协议最值得关注的设计哲学。


参考资源

标签