Skip to content

第21章 VLESS + TLS与REALITY ​

21.1 TLS认证服务器,VLESS选择代理账户 ​

一条代理连接至少有两个不同的身份问题:客户端现在连到的是否为预期代理服务器,以及服务端是否允许这位客户端使用代理。普通VLESS+TLS常用两层机制分别回答:TLS通过证书、名称验证与握手证明认证服务器,VLESS通过用户ID及账户策略决定代理用户。两层验证没有相互替代关系。

沿用Xray-core提交0888beb38a9ba45440557175e71ce21cf8ba3b13,先看RAW+TLS、普通CA证书、Flow为空、Encryption为none的职责模型。客户端连接代理入口后,发送TLS ClientHello。服务器返回证书链、CertificateVerify和Finished;客户端在信任根与参考名称下验证证书,再验证握手证明。只有相应TLS状态建立,VLESS请求字节才作为TLS应用数据交付给服务端。

服务器随后从VLESS头读取ID,按账户表查找,再处理Flow、命令和目标。普通部署不需要客户端持有一份CA签发证书;UUID就是这一层使用的账户凭据,但它没有变成TLS Certificate消息。若另行启用TLS客户端证书认证,那是增加了一个独立条件,不能把它描述为VLESS基本格式的一部分。

正在绘制图表…

查看图表原文
sequenceDiagram
    participant C as 代理客户端
    participant P as 代理服务器
    participant W as 目标网站
    C->>P: TLS ClientHello
    P-->>C: 证书链、握手签名、Finished
    C->>C: 验证信任链、参考名称与握手
    C->>P: TLS Finished及加密的VLESS请求
    P->>P: 解密,检查账户、Flow与目标策略
    P->>W: 建立目标连接并转发正文
    W-->>P: 目标返回字节
    P-->>C: 外层TLS中的VLESS响应与正文

图只展示职责和依赖,不把每个箭头规定成一个TLS记录或TCP段。证书本身通常公开,私钥留在服务器;客户端通过验证握手签名,确认当前对端能使用相应身份密钥。客户端的VLESS ID则应作为凭据保护,不能因为它叫UUID而认作不敏感的公开标识。

反方向看,服务器接受VLESS账户,并不证明客户端验证了服务器。如果客户端没有执行正确的服务器认证,主动中间人可能先与客户端建立自己掌握的TLS,再读取VLESS凭据。TCP连接成功、TLS加密算法强、ID足够随机,都不能补上遗漏的对端身份绑定。

证书验证还有一个常见配置误区:连接IP、参考名称、SNI和HTTP Host不是同一变量。客户端可拨号一个IP,同时按预期代理域名验证证书;这并不等于随意填一个热门域名都能通过。参考名称必须来自可信的服务配置,服务器证书应按相应规则覆盖它。Host只有在后续使用HTTP承载时才参与那一层,不能修补TLS名称验证失败。

固定TLS实现支持证书固定、指定验证名称等额外模式,它们会改变信任材料和验证路径。有些分支关闭库的默认验证,随后执行自己的检查,必须读完整回调才能判断是否失去认证。本节普通CA模型没有使用这些特殊模式,更不能把源码某处InsecureSkipVerify=true单独翻译成“所有Xray TLS都不验证证书”。

TLS保护的是其两个终止端之间的记录。若它终止在第20章的CDN,VLESS解析仍在源站,就要重新画出CDN可见内容及源站那一段连接;如果TLS直接终止在VLESS服务器,则旁路观察者看不到基本VLESS头的明文。两种情况下,浏览器访问网站的内层TLS仍是另一份端到端会话,代理外层证书不是网站证书。

本章讨论TLS 1.3时,标准状态沿用第16章所列现行RFC9846与历史RFC8446/8448基线;具体Xray行为依据RAW拨号器、TLS配置与验证回调及VLESS入站。下一节先拆开Hello中的名称与指纹,再看REALITY如何使用不同于普通CA证书验证的材料建立预期对端。

21.2 SNI、ALPN与指纹解决三个不同问题 ​

TLS ClientHello不仅携带密钥份额,还携带能力与服务选择信息。同样使用AES-GCM和TLS 1.3的两种客户端,可以发送不同的密码套件列表、扩展组合、扩展顺序、支持组与ALPN。对握手观察者而言,这些差异有助于分类客户端实现;对密码学而言,它们又未必改变认证目标。

SNI告诉服务器客户端想访问哪个名称,方便共享IP的入口选取服务或证书。它不是DNS解析结果,也不会把一条发往代理IP的TCP连接改为发往该名称的IP。在本章未使用加密ClientHello的模型中,旁路观察者可以读取SNI。把VLESS目标放在TLS里面,并不自动隐藏外层SNI这个入口名称。

ALPN协商应用协议。选择h2后,对端可能期待HTTP/2前言和帧;选择http/1.1后,HTTP承载应发送对应语法。只修改ALPN文本,不会把RAW的VLESS二进制头变成合法HTTP。ALPN声明与后续流量行为不一致,既可能影响兼容性,也可能成为可观察的差异。

指纹则描述ClientHello在这些维度上的结构特征。uTLS可以按某个浏览器系列的模板构造Hello,让扩展排列、套件组合等接近相应实现。固定Xray的GetFingerprint把chrome等名称映射到所依赖uTLS的ClientHelloID,空名称返回HelloChrome_Auto。Auto随依赖版本而定,不能把一个配置名看成永久固定的字节向量。

这类模板只覆盖其实现范围,不会把整个代理进程变成浏览器。TLS之后是否发送HTTP、HTTP/2 SETTINGS取什么值、请求字段如何排列、连接什么时候空闲或关闭,仍可能由其他库和代理逻辑决定。观察者也可能结合IP、时间、流量方向与多连接关系分类。修改Hello能改变部分特征,不能据此证明在所有观察能力下不可区分。

模板还必须与实际密钥材料和协议能力相配。supported_groups列表表示支持能力,key_share表示这次已携带的份额,ServerHello选择哪个组又是第三件事。第17章的X25519MLKEM768客户端份额为ML-KEM公钥1184字节加X25519公钥32字节;仅在支持列表里写出组号,却没有正确份额,并不能完成这个交换。

REALITY固定客户端先让uTLS构造握手状态,再取其中的X25519私有密钥材料用于自己的认证计算,同时修改Session ID。模板因此不只是外观选项:它需要生成实现能够取得、且服务端接受的相应份额。客户端本地Build成功,也不能证明服务端所有条件都满足。

尤其要记录本书固定版本的变化。Xray依赖的REALITY模块为83f621788ca88b83179810b12baa7ae518aacb83。其服务端扫描ClientHello时,要求先存在正确长度的X25519MLKEM768份额,再接受可选的独立X25519份额;若只看到旧式独立X25519而没有前述混合份额,会进入非授权处理路径。重复、异常顺序也有拒绝分支。这是该版本实际入口的约束,不应推广成所有历史REALITY握手的规范。

不过,客户端携带混合份额仍不等于最终TLS使用混合组。固定服务端随后从目标握手读取ServerHello,检查并沿用其选中的可接受组,再生成自己的对应交换材料。若最终选择的是X25519,就不能把日志中“ClientHello携带了ML-KEM”当成应用流量已经获得后量子保密的证明。

于是一次完整分析至少保留四项独立证据:配置选择了哪个模板,实际ClientHello携带什么,ServerHello选择什么,客户端最终通过了什么认证。名称、协商和身份在这里相互关联,却不能互相充当证明。

映射和模板名称见Xray tls.go,客户端握手状态使用见reality.go,特定份额约束及目标ServerHello检查见固定REALITY tls.go。本节没有生成一份冒称真实浏览器现行握手的虚构报文。

21.3 REALITY握手:目标参与外观,代理端建立自己的密钥 ​

REALITY仍利用TLS 1.3的握手和记录结构,却改变了对端识别方式及服务端对非授权连接的处理。理解它需要先区分四个角色:C为代理客户端,P为REALITY代理服务器,D为服务端配置的握手目标,W为VLESS最终请求访问的网站。D与W可以完全不同;配置里的target不是“用户这次想访问的任意网站”。

C预先持有P的X25519认证公钥及相应入口参数。P持有对应私钥、允许的SNI与shortId集合。C构造包含认证材料的ClientHello后,P尝试识别;识别成功并不直接令连接进入VLESS,还需要完成实际TLS密钥交换、服务端证明和客户端Finished。

固定REALITY模块的服务端入口首先拨号D,再用MirrorConn读取C的数据。MirrorConn在读取的同时将相应字节发给D,因此D能收到这次ClientHello,包括公开握手字段及看似普通的Session ID。P一边解析和验证客户端认证,一边准备处理D的回应。这个目标连接确实存在,不能把REALITY解释成只在本地凭空生成整段目标网站响应。

如果入口识别通过,P会检查D返回的ServerHello:必须符合所接受的TLS 1.3、套件与密钥份额类型等条件。随后P保留可用的握手结构,替换其中的服务端交换材料,生成自己的临时X25519密钥;若选择混合组,还对C发来的ML-KEM公钥作封装。由此形成C与P之间的会话共享秘密。

这一步决定了D的角色边界。D收到C的Hello,并提供实际返回结构与记录长度等参考;但P不需要知道D的TLS私钥,也不会从D那里取得它与C之间那次握手的秘密。合法代理连接使用P自己生成的交换材料,不能描述为“偷用目标证书的私钥”或“直接解密目标发来的所有TLS内容”。

P还创建带有REALITY验证材料的临时证书结构,用其掌握的Ed25519私钥完成TLS CertificateVerify,再发送Finished。C通过REALITY专用证书验证回调识别P;TLS库继续核对握手状态和Finished。验证通过后C发送自己的Finished,P验证完成,才把安全连接交给上层VLESS入站。

正在绘制图表…

查看图表原文
sequenceDiagram
    participant C as 客户端C
    participant P as REALITY服务器P
    participant D as 配置的握手目标D
    C->>P: 带认证材料的ClientHello
    P->>D: 镜像转发ClientHello
    P->>P: 检查名称、份额、认证和策略
    D-->>P: 目标ServerHello及后续记录
    P->>P: 生成自己的交换材料和REALITY证书
    P-->>C: 自己的ServerHello与加密握手
    C->>C: 专用对端验证及TLS握手校验
    C->>P: 客户端Finished
    P->>P: 验证完成,交付上层连接
    C->>P: 安全连接内的VLESS头及正文

图表示合法路径依赖,不规定网络到达竞争必定按某个线程顺序发生。模块使用锁和并发读取,在不能进入合法状态时走其他路径;若D先返回数据而P尚未建立所需状态,也可能转入直接转发。因此不能仅从密码学上的认证字段正确,就断言任何目标行为与时序都能成功。

外观适配也不止复制一个ServerHello。固定服务端记录目标各阶段的记录长度,自己的握手写出过程会使用相应长度状态;Xray RAW监听还启动目标握手后记录长度探测,模块按目标、名称及ALPN分类缓存这些资料。合法路径会参考它们产生相应记录。这是实现对可见结构的适配,不是证明与任意真实网站在全部流量特征上相同。

从密钥树看,应分成两件事:用于识别Hello和证书的AuthKey,来自客户端的X25519私钥材料与P的静态认证公钥;用于承载应用数据的TLS流量密钥,来自这次C—P实际协商的临时交换及TLS派生过程。两者职责不同,不应该把AuthKey直接画成TLS application_traffic_secret。下一节给出AuthKey和Session ID的具体计算。

这里的VLESS UUID尚未出现。REALITY入口即使允许某个shortId,后续VLESS仍需账户检查。相反,只有UUID而缺少匹配的REALITY入口条件,也不能跳过握手进入VLESS。两个认证层的失败位置、管理范围和泄露影响应分别分析。

本节依据固定模块Server与MirrorConn、自定义服务端握手、握手后长度探测,以及Xray客户端验证回调。这是固定源码路径分析,没有宣称已对某个公网目标实测成功。

21.4 认证材料藏在哪里:32字节Session ID与两份证明 ​

普通TLS 1.3的legacy_session_id主要承担兼容角色,不能仅凭这个名称假定它在REALITY里就是一张恢复票据。固定REALITY客户端把它设置成32字节,实际放入16字节认证明文经过AES-GCM处理后的密文与标签。

先定义材料。P的静态认证私钥为s,对应X25519公钥为S。C从uTLS这次握手状态取得可用的X25519私钥x,其公钥份额记为X;服务器取对应公开份额。客户端优先使用独立X25519密钥,必要时使用混合份额中的X25519部分,不能将ML-KEM公钥整个送进X25519函数。

两端计算相同的Z_auth:客户端为X25519(x,S),服务端为X25519(s,X)。然后用HKDF-SHA256派生:

text
AuthKey = HKDF-SHA256(
    输入密钥材料 = Z_auth,
    salt = ClientHello.random的前20字节,
    info = ASCII字符串REALITY,
    输出长度 = 32字节)

此处使用普通HKDF提取与扩展,不是TLS的HKDF-Expand-Label:info前面没有自动加tls13前缀或TLS标签长度结构。32字节结果使随后使用的AES-GCM采用256位AES密钥。ClientHello.random仍为32字节,其后12字节用作该GCM调用的Nonce,与前20字节的salt职责不同。

加密前的16字节内容为:

相对认证明文的偏移长度内容
0—23Xray版本三个分量,各一字节
31保留字节,客户端写0
4—74Unix秒时间戳,大端
8—158shortId,配置解码后的字节

shortId配置中的十六进制文字不是逐字符放进这里。固定配置支持至多16个十六进制字符,解码到一个8字节数组;不足8字节的剩余位置保持零。它的作用是服务端允许集合中的匹配值,不是VLESS UUID,也不是用于TLS应用记录的密钥。

关联数据AAD也需要精确说明:客户端先把ClientHello原始握手字节中的32字节Session ID置零,以这一整份Hello作为AAD。握手字节包括ClientHello消息头,不包括外面的TLS记录头。然后计算:

text
SessionID密文与标签 = AES-256-GCM.Seal(
    key = AuthKey,
    nonce = random[20:32],
    plaintext = 上表16字节,
    AAD = Session ID内容为32个零的ClientHello握手字节)

16字节明文加16字节标签得到32字节,恰好填回Session ID。长度字节仍为20(十六进制32),位于该32字节之前。固定客户端在握手消息偏移39开始替换内容,偏移来自4字节握手头、2字节兼容版本、32字节random和1字节Session ID长度;这是本实现已构造的32字节Session ID布局,不能任意用于不同长度Hello。

服务端先保存收到的32字节,把对应原始Hello区域归零,再以相同Nonce和AAD执行Open,成功后恢复原始密文字节。归零规则解决了一个看似循环的问题:不能用最终包含标签的Hello当作生成同一标签所需的AAD。两端必须对哪一份字节求认证完全一致。

解密成功后,还要检查入口名称、份额条件、版本范围、时间差和shortId集合。固定服务端MaxTimeDiff=0表示不执行这个时间差限制,非零值才比较绝对时间差;配置传入单位为毫秒,认证明文的时间戳单位却是秒。时间窗口也不是一个“已见Hello集合”,不能因有时间戳就宣称每个Hello只会被接受一次。

客户端怎样反向识别P?固定服务端生成Ed25519公钥Q,并把证书末尾原本存放64字节签名的区域替换成HMAC-SHA512(AuthKey,Q)。客户端证书回调取证书中的Ed25519公钥,重新计算并比较这64字节,成功才设置REALITY的Verified状态。这是专用验证约定,不是用公共CA签发一张覆盖目标域名的普通证书。

随后TLS的CertificateVerify仍使用对应Ed25519私钥对握手上下文签名,Finished仍验证TLS共享秘密与转录。HMAC让客户端将临时证书公钥绑定到本次AuthKey;CertificateVerify证明当前对端掌握该临时公钥对应私钥;Finished则绑定握手和交换结果。只说“REALITY用一张自签名证书”会省掉真正决定接受的专用验证规则。

这个版本另有可选ML-DSA-65附加证明。ML-DSA是后量子数字签名,与第17章负责共享秘密的ML-KEM不同,不能用它加密正文。服务端用独立种子生成签名密钥,在专门证书扩展内放入签名;签名消息是对AuthKey为键、依次输入Q、原始ClientHello、原始ServerHello所得HMAC-SHA512结果。客户端配置了相应验证公钥时,除了前述HMAC还必须验证这份扩展签名,才能设置Verified。配置未启用时,不能凭代码支持它就说当前会话已使用后量子身份认证。ML-DSA标准FIPS204

最后,REALITY入口条件不应被误称为每用户客户端证书认证:知道公开算法、公钥入口参数与允许shortId的连接方,可以生成自己的临时份额并形成认证Hello;这里没有要求每个用户用一把独有私钥签名。上层VLESS账户仍承担其用户授权职责。哪些配置材料需要按访问凭据限制分发,应与其数学上属于公钥还是私钥分开讨论。

客户端计算及验证见Xray reality.go,参数解码见transport_security.go,服务端Open和条件检查见REALITY tls.go,证书HMAC及附加签名见handshake_server_tls13.go。

21.5 非授权握手:转发到目标与接受代理是不同分支 ​

一位普通浏览器用户连接P,却不携带有效REALITY材料时,会发生什么?固定服务端已经向D镜像了首批输入。若不能建立合法REALITY状态,相应路径继续复制C与D之间的字节,使D自己处理TLS及后续应用协议。此时C所看到的证书、握手与网站内容可以真实来自D,P无需持有D的证书私钥。

这条路径的密码学端点是C与D。P作为转发节点可以看见未加密的握手字段和密文流量特征;若C正确验证D的证书与握手,P不因参与转发就获得D的TLS应用密钥。它与21.3的合法代理路径不同:合法路径由P生成自己的交换材料和专用证书,TLS端点是C与P。

text
合法REALITY代理:C ←安全会话→ P → VLESS选出的目标W
普通目标转发:  C ←────TLS安全会话────→ D
                         途中经过P转发

因此D不是一个给未知用户任意拨号的通用出口。它来自P的服务端配置;普通输入没有取得VLESS账户,也没有在基本VLESS目标字段里指定另一个W。能够访问D网站,不等于通过P获得访问任意地址的代理权限。

也不是任何输入都一定收到一张漂亮的网站证书。D可能不可达、拒绝SNI、选择本版本不接受的握手结构,或者直接关闭连接;首批数据可能不是TLS。固定实现还包含不同阶段的转发和失败分支,有些已进入自建握手后发生的错误会结束连接。不能将“非授权路径使用目标转发”扩写成“所有格式错误、所有时序和所有目标都能无缝回退”。

REALITY专用客户端对真实网站证书的处理尤其重要。其VerifyPeerCertificate回调先尝试专用HMAC及可选附加签名。如果不匹配,就按ServerName构造普通X.509验证选项,校验证书链。这一步若失败,TLS握手失败;若普通证书有效,回调可以返回成功,但不会设置REALITY专用的Verified=true。

握手返回后,UClient再次检查Verified。只有专用验证成功,才把连接返回给VLESS。普通CA证书通过、专用验证没通过时,固定代码会记录“收到真实证书”的路径,启动HTTP/2访问行为,按spider相关设置获取路径及发送普通请求,最终返回连接处理错误;它不会把这条连接作为已认证REALITY通道继续给VLESS发送UUID和目标。

这是一道防止认证降级的边界。假设一个主动转发者把客户端改送到同名真实网站,公共CA验证可能完全正确,却没有证明对端是持有预期REALITY私钥的P。若只要证书有效就继续发送代理凭据,就会混淆“这是该网站”与“这是预期代理”。固定客户端的第二个Verified检查正是将两种成功分开。

这里的普通HTTP访问行为应与协议认证本身区分。它不会把不匹配的P公钥修复成匹配,也不能保证浏览特征与某个真人浏览器一致。具体抓取、并发、等待和路径处理受版本及配置影响,不能拿spiderX的一个路径值当作REALITY的密钥或代理目标。

另一边,VLESS层的fallbacks发生在已经交给VLESS的连接内,判断基本版本或ID等条件;REALITY非授权处理发生在安全承载入口。两者的首读、目标配置、消费字节和失败范围不同。部署中看到“回退到网站”,必须先判断是哪一层在做决定,才能解释网站证书由谁提供、VLESS头有没有出现。

非授权转发与阶段失败见固定REALITY Server,普通证书验证、Verified检查及后续HTTP访问见Xray客户端。上述端点和降级结论基于这两条实际调用路径,不以“回退”一词代替实现解释。

21.6 分开评价入口认证、前向保密与可观测性 ​

REALITY中最容易混淆的是静态AuthKey来源与临时TLS交换。它们虽然可能使用同一客户端份额中的X25519材料,却不使用同一服务端私钥,最终也不产生同一个用途的密钥。

若攻击者完整保存一次握手,之后取得P的静态X25519认证私钥s,就可用公开客户端份额重算Z_auth和AuthKey,并打开当时Session ID中的版本、时间与shortId。这一小段认证明文没有相对于s泄露的前向保密性。但已经完成的应用记录,还依赖P当时另行生成的临时交换及TLS密钥树;单凭s不等于取得那份临时私钥,也不自动解密过去网站正文。

泄露或攻击条件直接影响不能仅由此推出
静态认证私钥s泄露可重算保存握手的AuthKey,破坏依赖它的服务器认证边界所有过去TLS应用记录已可解密
VLESS UUID泄露上层账户凭据暴露;入口条件满足时可能滥用账户攻击者已经知道REALITY静态私钥
某次完整会话流量密钥泄露该密钥覆盖的记录失去保密性所有其他连接的临时密钥也泄露
单独公开P的认证公钥它按设计参与客户端ECDH计算已能由公钥计算P的私钥或任意受害会话AuthKey

静态私钥泄露对未来主动仿冒更严重。未启用独立ML-DSA附加验证时,攻击者可以为新连接建立自己掌握的临时TLS交换,再计算被客户端接受的AuthKey证明。若客户端要求独立的ML-DSA验证且其签名私钥仍安全,攻击者还缺少对应Hello绑定的附加签名。这里不能把“增加一种后量子算法”泛化成免疫所有端点失陷;泄露签名种子、篡改客户端可信公钥或直接控制进程,仍会改变结论。

后量子保密也要看最终协商。若C—P实际使用X25519MLKEM768并正确组合两个共享秘密,未来单独攻破X25519并不直接得到过去TLS流量秘密;若实际选的是纯X25519,则不能作同样判断。AuthKey自身依然来自X25519,不能因为旁边存在ML-KEM就改称“后量子AuthKey”。后量子身份抗仿冒与过去记录的后量子保密,是两项不同属性。

再看重放。固定入口的时间差检查可以排除窗口外的认证明文;MaxTimeDiff为0时连这项限制也不执行。本章所读认证路径没有维护“每个Hello只接受一次”的一次性缓存,因此相同Hello在允许条件内可能再次通过入口检查。AES-GCM认证消息内容,并不自动识别它此前是否来过。

但重放一个ClientHello也不等于重放一个已经成功的代理会话。服务端仍建立新的临时握手状态,要求相应客户端Finished;只保存旧公开Hello而不知道客户端秘密的攻击者,不能照抄旧Finished就保证通过新的转录和密钥验证。固定客户端也没有把VLESS应用数据当TLS 0-RTT发送。应该分别讨论入口可被触发、资源消耗、完整握手完成和应用操作重放,不能互相替代。

对旁路观察者,REALITY没有隐藏P的IP、TCP连接、报文长度、方向和时间。未使用加密ClientHello的本章路径也暴露外层名称、协商能力及公开密钥份额。入口认证材料装入Session ID,可以避免直接出现shortId明文,却不能抹掉所有统计特征;服务端与D的连接也有自己的网络可见性。

对D而言,它收到镜像的ClientHello,通常看到P作为TCP对端;若明确配置并由D理解PROXY协议,还可能获得额外来源信息。合法路径中D不是VLESS解密端,不应把它描述成自动知道用户最终访问W。对P而言,REALITY记录终止于P,它能解析VLESS目标和账户;网站内层TLS及可选的其他上层保护再决定P能看到多少应用内容。

最后,认证不等于可用性。固定Server首先拨号D,目标连接失败便无法按该路径服务;握手外观还依赖目标返回结构和缓存探测。CPU、连接数、目标带宽及攻击触发的资源占用,都需要单独约束。把REALITY称为一个单纯“无需证书的TLS开关”,会掩盖这份实际运行依赖。

这些结论来自前面已展开的AuthKey入口、独立临时交换与证书证明及Xray Verified检查,属于明确泄露条件下的机制分析,不是对流量不可识别性的实验结论或形式化安全证明。

21.7 为什么普通CDN终止不了预期的REALITY身份 ​

REALITY客户端认的是预先配置的认证材料所绑定的P,而普通HTTPS CDN通常在边缘E终止TLS,出示CA证书,再向源站P建立另一份连接。如果将这两种模式直接混在一起,C收到的是E的普通证书,不是与本次AuthKey匹配的专用证明。即使证书名称正确,21.5的Verified检查仍不会将它作为REALITY代理通道交给VLESS。

这不是因为“CDN不支持长连接”这样笼统的原因。即使E支持无限长HTTP流,认证端点依然已经变化。E若将其收到的HTTP内容重新发给P,P也收不到原本C—E那份完整、未经终止的REALITY握手;新的TLS ClientHello由E生成,临时密钥与转录都不同。复制一个SNI或Session ID字符串,不能把两份握手拼成同一份受认证会话。

若E提供透明TCP转发,把握手字节原样交给P而不终止它,则C—P的REALITY身份与密钥建立可以继续成立。E此时承担的是四层转发角色;它不能同时按照这份REALITY内部HTTP的解密路径作普通七层路由。某个供应商既有HTTP CDN又有四层产品,并不使两种功能具有相同信任边界。

这也解释了XHTTP+REALITY的含义。XHTTP描述REALITY安全连接内部使用HTTP组织逻辑字节,而REALITY负责C与P的握手端点;它不是“因为XHTTP使用HTTP,所以中间普通CDN可以替C终止REALITY”。固定XHTTP拨号器遇到REALITY设置会选择HTTP/2路径,不能再根据XHTTP一般支持HTTP/3,就宣称当前组合使用QUIC上的REALITY。

固定StreamConfig构建器允许REALITY用于内部tcp、splithttp或grpc传输,其他传输进入组合错误。配置上允许,是对应实现路径存在的一个条件;它不替代对目标握手、ALPN、模板和实际服务的核验。尤其不能从历史文章里复制VLESS+REALITY+WebSocket的任意叠加关系,假定本提交也接受。

如果希望普通HTTP边缘转发,承载与安全设计必须符合边缘的终止模型,例如先让边缘以普通TLS处理HTTP,再检查源站与可选独立端点加密保护的边界。第22章的VLESS Encryption位于VLESS处理器中,与“外层安全握手是谁终止”是不同维度;但是否需要、是否兼容某种组合,仍需按具体路径分析,不能在未检查配置前用它补全所有任意组合。

还有一种看似相近的结构是前置服务器把C的TCP连接转发到远处P,而P再向D拨号。它能改变连接的网络入口,却不会让D变成VLESS落地,也不会使前置节点自动得到REALITY流量密钥。若前置修改了字节、终止了握手或发了P不接受的前缀,结果又会不同。代理链分析中的“透明转发”必须在这一层实际成立。

组合约束见StreamConfig.Build,REALITY的H2选择见decideHTTPVersion,普通证书与专用验证的区别见Xray reality.go。本节说明的是协议端点与固定实现约束,没有对某家CDN产品给出未经验证的支持列表。

21.8 一组可核对的认证计算向量 ​

下面构造一份认证计算输入,用于核对21.4的长度、AAD与派生顺序。它不冒充真实浏览器指纹,也没有完成与目标D的网络握手。本书仅在作者内部复算密码运算,不给读者提供实验程序。所有私钥材料来自已公开标准向量,不能用于真实服务。

X25519选择第15章RFC7748 §6.1的Alice作为客户端x,Bob作为P的静态s;其公开公钥分别为X与S。两向交换得到:

text
Z_auth = 4a5d9d5ba4ce2de1728e3bf480350f25
         e07e21c947d19e3376f09b3c1e161742
random = 000102030405060708090a0b0c0d0e0f
         101112131415161718191a1b1c1d1e1f

random是为复算选取的固定递增字节,真实连接应使用适当的新鲜随机材料。前20字节止于13,Nonce为14 15 16 17 18 19 1a 1b 1c 1d 1e 1f;切分单位是字节,不能把20误解为十六进制20后切到第32字节。

认证明文取1a 0a 0a 00 | 65 53 f1 00 | 11 22 33 44 55 66 77 88:前三字节为固定Xray源码版本分量26、10、10,不是这次Git提交日期;时间戳为1700000000秒,是特意固定的旧时刻;shortId为示例八字节。它不表示当前部署应接受这个时间。

为使AAD完全确定,构造如下ClientHello。ML-KEM公钥使用第17.7节固定NIST keyGen的tg2/tc26那一份1184字节ek,其SHA-256为4158f6afb5e516c99f1da07da8c651348422b17c1f4e9a08ad73fb1f91249b3e;不是同节另一份encapDecap tc26的公钥。

顺序字段精确内容或长度
1Handshake头01,三字节正文长000573
2兼容版本、random0303,上述32字节random
3Session ID长度20,随后32个零,作为AAD时保持全零
4密码套件0004 1301 1303
5压缩方法0100
6扩展总长度0526,即1318字节
7server_name0000,内容长0010,名称列表000e 00 000b,随后example.com
8supported_groups000a 0006 0004 11ec 001d
9signature_algorithms000d 0006 0004 0807 0804
10supported_versions002b 0003 02 0304
11key_share0033,内容长04ea,列表长04e8;先11ec 04c0加ek和X,再001d 0020加X
12ALPN0010 0005 0003 02 6832,即h2

同一个ClientHello中两种份额复用X的选择符合第17章已区分的同次混合份额规则;这个构造不重复使用真实生产临时私钥。其扩展长1318,Hello正文1395,含四字节握手头的AAD总长1399。AAD前缀为01 00 05 73 03 03,零Session ID占握手偏移39—70。

这份1399字节AAD的SHA-256为:

text
96ed28df6cbc2b4323fbc3279c1ca219
15c50593f988468a3a275b7aee4dcbc6

摘要本身有64个十六进制字符,拼接两行才是完整值。摘要用于核对输入一致性,不是传给GCM的AAD替代品。实际Seal仍输入整份1399字节Hello。

以Z_auth、前20字节salt及ASCII的REALITY派生,AuthKey为:

text
68e5a4d6fbfc0f93477d737fbdd45bd5
f81578fbd172327b6db8e963e2ba4a3c

AES-GCM输出的32字节Session ID为:

text
16 14 cb d1 68 da f2 21 26 11 74 fa 7e d8 bd d8
5c f2 6c 24 5b 7f 0a ff f5 04 83 72 75 bf eb 3f

第一行是16字节密文,第二行是16字节认证标签,二者都放在Session ID里。把这32字节填回Hello后,握手总长仍为1399,不能额外在尾部再追加同一份标签。服务端解密前再将该区域置零,就能重建相同AAD,恢复原16字节内容。

作者内部用双向X25519核对Z,用独立的HKDF提取/扩展计算核对库结果,再用Python密码库和Node/OpenSSL核对相同GCM输出;修改密文、AAD或Nonce的例子均被拒绝。这些检查验证本组数值与密码接口,不证明完整Xray互操作、目标适配、浏览器指纹或ML-DSA附加验证。后几项仍须依赖相应完整状态路径,不能由一个正确标签越级证明。

公开输入来源为RFC7748 §6.1与固定NIST keyGen投影;REALITY专用组合规则来自前述固定源码。本节是本书构造向量,不是官方REALITY发布的测试向量。

21.9 安全连接建立之后,还可能在哪一步失败 ​

考虑同一客户端配置的三个变化。第一种,REALITY静态公钥正确、shortId正确、VLESS账户已被删除。客户端仍可能完成REALITY专用验证,但服务端读到VLESS ID时查不到账户,于是不会按该请求建立任意目标连接。入口安全通道与用户授权在不同时间作出判断,后者失败不会反过来使前面的密码运算“没有加密”。

第二种,REALITY和账户都正确,但客户端请求普通TCP且Flow为空,账户却要求Vision。固定入站在相应分支拒绝,不能只凭UUID匹配就忽略Flow策略。这里应检查请求与账户的组合,而不是更换证书或怀疑DNS。加密通道完好与协议选项不兼容可以同时发生。

第三种,REALITY静态公钥配置错误,客户端却被转到一个具有有效公共CA证书的网站。普通证书验证可能通过,但REALITY Verified仍未通过,不应将VLESS凭据发给这个对端。这个现象也不意味着真实网站证书有问题,而是它证明了另一种身份,不能满足预期代理身份。

已有证据可以支持的判断仍需要的下一层证据
TCP连接建立某个入口接受了连接预期安全对端是否得到认证
普通CA证书验证成功在该验证策略下符合参考名称及信任链REALITY是否通过专用验证
REALITY Verified且TLS握手完成专用代理通道满足本次握手检查VLESS用户与命令是否被允许
VLESS头成功解析已得到基本请求字段与账户Flow、目标路由和出站是否成功
网站自身TLS认证成功浏览器建立了到预期网站的受认证会话网站应用是否接受并完成请求

“网站自身TLS认证成功”同样依赖浏览器使用正确参考名称和信任策略。若用户主动信任了另一个拦截证书体系,内层可见性就与本章默认模型不同。代理协议不能代替网站登录、支付确认或应用层完整性;哪怕每层握手都成功,也可能收到真实网站返回的403或业务错误。

性能判断也应遵循这份分层。REALITY避免普通公共CA证书部署的一些管理要求,却没有消除TLS交换、签名、密钥派生、目标连接或应用数据加解密。目标镜像与外观处理还引入相应依赖。若要比较TLS和REALITY,至少需要相同硬件、目标、模板、最终协商组、是否附加ML-DSA,以及连接复用和负载条件,不能把“无需购买证书”直接换算成吞吐率优势。

到这里仍然没有解释VLESS Encryption。REALITY的AuthKey、Session ID和专用证书验证,不是VLESS Encryption的握手格式;后者有自己的配置解析、混合密钥交换、记录边界与恢复状态。两者可能在同一配置讨论中出现,数学原语也会有重叠,但字段位置、密钥生命周期和失败边界必须从各自实现重新建立。下一章从这条分界开始。

Flow与账户拒绝条件可在固定VLESS入站核对。本章仅对列明提交和路径作出结论;将来REALITY改变握手份额要求、证书扩展或目标适配时,应沿对应调用链重新核验,不能因名称相同沿用全部旧判断。

从字节与状态机出发,理解每一层的职责。