Skip to content

REALITY 安全层

简介

REALITY 是一种自定义安全协议,扩展了 TLS 1.3,使服务端能够伪装成任何真实的 TLS 服务器而无需其证书或私钥。REALITY 服务端不呈现自己的证书,而是将 TLS 握手代理到合法的"dest"服务器。授权客户端(知道服务端公钥和 Short ID)可以通过嵌入在 TLS 1.3 ClientHello Session ID 中的自定义方案验证服务端。未授权客户端或主动探测者收到来自 dest 服务器的真实证书,使代理与真实服务无法区分。无需证书颁发机构。

关键文件

  • transport/internet/reality/reality.go -- 客户端(UClient)和服务端(Server)连接逻辑
  • transport/internet/reality/config.go -- GetREALITYConfig()、配置提取

架构

mermaid
sequenceDiagram
    participant C as REALITY 客户端
    participant S as REALITY 服务端
    participant D as Dest 服务器(如 google.com)

    Note over C: 已知:PublicKey、ShortId、ServerName
    Note over S: 已知:PrivateKey、ShortIds[]、ServerNames[]

    C->>S: TLS ClientHello (SessionId = 加密认证)
    S->>S: 解密 SessionId,验证 ShortId + 时间戳
    alt 授权客户端
        S->>C: TLS ServerHello (包含用 AuthKey 签名的 ed25519 证书)
        C->>C: 用 AuthKey 验证证书签名
        Note over C,S: 已认证的 REALITY 连接
    else 未授权 / 探测
        S->>D: 转发 ClientHello
        D->>S: 真实 ServerHello + 证书
        S->>C: 转发真实响应
        Note over C,S: 客户端看到真实的 google.com 证书
        Note over S: 连接变为到 Dest 的透明代理
    end

客户端实现

UClient

reality.UClientreality/reality.go:117-277)执行 REALITY 握手:

步骤 1:构建 uTLS 连接

go
func UClient(c net.Conn, config *Config, ctx context.Context, dest net.Destination) (net.Conn, error) {
    uConn := &UConn{Config: config}
    utlsConfig := &utls.Config{
        VerifyPeerCertificate:  uConn.VerifyPeerCertificate,
        ServerName:             config.ServerName,
        InsecureSkipVerify:     true,
        SessionTicketsDisabled: true,
    }
    fingerprint := tls.GetFingerprint(config.Fingerprint)
    uConn.UConn = utls.UClient(c, utlsConfig, *fingerprint)

InsecureSkipVerifytrue,因为验证由 REALITY 自定义的 VerifyPeerCertificate 处理,而非 Go 内置的证书链验证。

步骤 2:构建认证 Session ID

Session ID(32 字节)被构建并加密(reality.go:138-176):

go
hello := uConn.HandshakeState.Hello
hello.SessionId = make([]byte, 32)

// 字节 0-2:Xray 版本
hello.SessionId[0] = core.Version_x
hello.SessionId[1] = core.Version_y
hello.SessionId[2] = core.Version_z
hello.SessionId[3] = 0 // 保留

// 字节 4-7:当前 Unix 时间戳(大端序)
binary.BigEndian.PutUint32(hello.SessionId[4:], uint32(time.Now().Unix()))

// 字节 8-15:Short ID(8 字节)
copy(hello.SessionId[8:], config.ShortId)

步骤 3:派生 Auth Key

Auth Key 从 ECDH 共享密钥派生(reality.go:152-169):

go
publicKey, _ := ecdh.X25519().NewPublicKey(config.PublicKey)
ecdhe := uConn.HandshakeState.State13.KeyShareKeys.Ecdhe
// 对于后量子密钥交换回退到 MlkemEcdhe
uConn.AuthKey, _ = ecdhe.ECDH(publicKey)

// HKDF 派生
hkdf.New(sha256.New, uConn.AuthKey, hello.Random[:20], []byte("REALITY")).Read(uConn.AuthKey)

HKDF 使用 ClientHello.Random 的前 20 字节作为盐值,字面量字符串 "REALITY" 作为信息。

步骤 4:加密 Session ID

go
aead := crypto.NewAesGcm(uConn.AuthKey)
aead.Seal(hello.SessionId[:0], hello.Random[20:], hello.SessionId[:16], hello.Raw)
  • NonceClientHello.Random 的后 12 字节(字节 20-31)
  • 明文:Session ID 的前 16 字节
  • 附加数据:完整的原始 ClientHello
  • 输出:16 字节密文 + 16 字节 GCM 标签 = 覆写完整的 32 字节 Session ID

加密后的 Session ID 随后被复制回原始 ClientHello 字节中(reality.go:175)。

步骤 5:验证服务端证书

VerifyPeerCertificatereality/reality.go:76-115)检查服务端是否为真实的 REALITY 服务端:

go
func (c *UConn) VerifyPeerCertificate(rawCerts [][]byte, _ [][]*x509.Certificate) error {
    certs := // 解析 rawCerts
    if pub, ok := certs[0].PublicKey.(ed25519.PublicKey); ok {
        h := hmac.New(sha512.New, c.AuthKey)
        h.Write(pub)
        if bytes.Equal(h.Sum(nil), certs[0].Signature) {
            // 可选的 ML-DSA-65 后量子验证
            if len(c.Config.Mldsa65Verify) > 0 {
                // 验证 ClientHello + ServerHello 上的 ML-DSA-65 签名
            }
            c.Verified = true
            return nil
        }
    }
    // 回退到标准 x509 验证(来自 dest 服务器的真实证书)
    opts := x509.VerifyOptions{DNSName: c.ServerName, ...}
    certs[0].Verify(opts)
    return nil  // 验证通过(是真实证书),但 Verified 保持 false
}

REALITY 服务端生成的 ed25519 证书具有以下特征:

  • 公钥:ed25519 公钥
  • 签名HMAC-SHA512(AuthKey, PublicKey)

如果此 HMAC 匹配,客户端确认正在与真实的 REALITY 服务端通信。如果不匹配,证书按正常方式验证(来自 dest 服务器),但 c.Verified 保持 false

步骤 6:处理验证失败

如果握手后 uConn.Verifiedfalsereality.go:183-274),客户端模拟真实浏览器行为:

go
if !uConn.Verified {
    // "蜘蛛"模式:爬取 dest 服务器以生成逼真流量
    client := &http.Client{Transport: &http2.Transport{...}}
    // GET 页面,跟随链接,添加 cookie
    // 使连接看起来像真正的浏览器在访问网站
    time.Sleep(randomDuration)
    return nil, errors.New("REALITY: processed invalid connection")
}

蜘蛛以逼真的时序爬取 dest 服务器的页面,然后关闭连接。这使得主动探测无法通过时序分析与真实浏览器访问区分。

服务端连接

reality.Serverreality/reality.go:52-55)使用 reality 库包装:

go
func Server(c net.Conn, config *reality.Config) (net.Conn, error) {
    realityConn, err := reality.Server(context.Background(), c, config)
    return &Conn{Conn: realityConn}, err
}

实际的服务端逻辑在 github.com/xtls/reality 库中,它:

  1. 读取 ClientHello
  2. 使用私钥解密 Session ID
  3. 验证 Short ID、时间戳和客户端版本
  4. 如果授权:使用共享 AuthKey 生成 ed25519 证书签名
  5. 如果未授权:透明代理到 dest 服务器

配置

客户端配置

来自 reality/config.go:74-83

go
func ConfigFromStreamSettings(settings *internet.MemoryStreamConfig) *Config {
    config, ok := settings.SecuritySettings.(*Config)
    if !ok { return nil }
    return config
}

客户端需要:

  • ServerName:TLS 握手的 SNI(被伪装的服务器)
  • Fingerprint:uTLS 指纹(必填,不能为空)
  • PublicKey:服务端的 X25519 公钥(32 字节)
  • ShortId:客户端的 Short ID(最多 8 字节)
  • SpiderX:蜘蛛爬取的起始路径
  • SpiderY:包含 10 个 int64 值的数组,控制蜘蛛行为(cookie 填充、并发数、间隔等)
  • Show:调试模式,将 REALITY 状态输出到标准输出
  • Mldsa65Verify:用于后量子验证的 ML-DSA-65 公钥

服务端配置

GetREALITYConfigreality/config.go:16-58)构建 reality.Config

go
func (c *Config) GetREALITYConfig() *reality.Config {
    config := &reality.Config{
        Show: c.Show,
        Type: c.Type,
        Dest: c.Dest,                    // dest 服务器地址
        Xver: byte(c.Xver),              // PROXY 协议版本

        PrivateKey:   c.PrivateKey,       // X25519 私钥
        MinClientVer: c.MinClientVer,     // 最低客户端版本
        MaxClientVer: c.MaxClientVer,     // 最高客户端版本
        MaxTimeDiff:  time.Duration(c.MaxTimeDiff) * time.Millisecond,

        SessionTicketsDisabled: true,
    }
    // 填充 ServerNames 映射和 ShortIds 映射
    config.ServerNames[serverName] = true
    config.ShortIds[shortId] = true
    // 可选的 ML-DSA-65 签名密钥
    // 可选的回退连接速率限制
    return config
}

服务端需要:

  • PrivateKey:X25519 私钥(32 字节)
  • ServerNames:允许的 SNI 值
  • ShortIds:允许的 Short ID([8]byte 映射)
  • Dest:未授权客户端代理到的真实服务器地址
  • MaxTimeDiff:允许的最大时钟偏差(毫秒)
  • MinClientVer / MaxClientVer:允许的 Xray 客户端版本范围

Session ID 线格式

字节  0-2:  Xray 版本 (x.y.z)
字节  3:    保留 (0)
字节  4-7:  Unix 时间戳(大端序 uint32)
字节  8-15: Short ID(8 字节,可能部分为零)

[使用 AES-GCM 和 AuthKey 加密]
[Nonce = ClientHello.Random[20:32]]
[AAD = 原始 ClientHello 字节]

服务端解密后:

  1. 检查 Short ID 是否在允许列表中
  2. 验证时间戳在 MaxTimeDiff 范围内
  3. 检查客户端版本在允许范围内

后量子支持

密钥交换

REALITY 在 TLS 指纹可用时支持 X25519-MLKEM768 密钥交换(reality.go:156-161):

go
ecdhe := uConn.HandshakeState.State13.KeyShareKeys.Ecdhe
if ecdhe == nil {
    ecdhe = uConn.HandshakeState.State13.KeyShareKeys.MlkemEcdhe
}

证书验证

可选的 ML-DSA-65(Dilithium)验证(reality.go:88-95):

go
if len(c.Config.Mldsa65Verify) > 0 {
    h.Write(c.HandshakeState.Hello.Raw)
    h.Write(c.HandshakeState.ServerHello.Raw)
    verify, _ := mldsa65.Scheme().UnmarshalBinaryPublicKey(c.Config.Mldsa65Verify)
    if mldsa65.Verify(verify.(*mldsa65.PublicKey), h.Sum(nil), nil, certs[0].Extensions[0].Value) {
        c.Verified = true
    }
}

这提供了后量子认证:服务端用其 ML-DSA-65 私钥签名 HMAC(AuthKey, PublicKey || ClientHello.Raw || ServerHello.Raw),客户端用预共享的公钥进行验证。

密钥日志支持

客户端和服务端均支持 TLS 密钥日志以供调试(reality/config.go:61-72):

go
func KeyLogWriterFromConfig(c *Config) io.Writer {
    if len(c.MasterKeyLog) <= 0 || c.MasterKeyLog == "none" { return nil }
    writer, _ := os.OpenFile(c.MasterKeyLog, os.O_CREATE|os.O_RDWR|os.O_APPEND, 0644)
    return writer
}

传输协议集成

REALITY 在两个点与传输协议集成:

客户端

每个传输协议检查 REALITY 配置并应用:

go
// tcp/dialer.go:89-93
if config := reality.ConfigFromStreamSettings(streamSettings); config != nil {
    conn, err = reality.UClient(conn, config, ctx, dest)
}

服务端

传输协议包装其监听器:

go
// tcp/hub.go:76-79
if config := reality.ConfigFromStreamSettings(streamSettings); config != nil {
    l.realityConfig = config.GetREALITYConfig()
    go goreality.DetectPostHandshakeRecordsLens(l.realityConfig)
}

DetectPostHandshakeRecordsLens 在启动时调用,用于对 dest 服务器的握手后行为进行指纹识别,以实现更准确的伪装。

实现说明

  • 无需 CA:REALITY 不需要任何证书。服务端要么呈现 dest 服务器的真实证书(给未授权客户端),要么呈现动态生成的 ed25519 证书(给授权客户端)。
  • 主动探测抵抗:未授权连接被透明代理到 dest 服务器。提供 dest 服务器的真实证书,使探测无法与直接连接区分。
  • 蜘蛛反指纹:当 REALITY 客户端收到真实证书(表示探测或配置错误)时,它像真正的浏览器一样爬取 dest 服务器,然后断开连接,防止基于时序的检测。
  • 指纹必填:与 TLS 不同,REALITY 要求 uTLS 指纹(reality.go:133-136)。不能使用标准 Go TLS,因为 REALITY 需要访问 ClientHello 内部结构。
  • Session ID 固定位置:Session ID 占据原始 ClientHello 第 39 字节之后的位置(reality.go:142),这是 TLS 1.3 ClientHello 结构中的固定位置。
  • 时钟同步:客户端和服务端时间戳必须在 MaxTimeDiff 范围内(默认值因配置而异)。较大的时钟偏差将导致认证失败。
  • Short ID 作为访问控制:不同的 Short ID 可以分配给不同的用户/客户端,实现无需更换密钥的逐用户访问控制。
  • 速率限制LimitFallbackUpload/LimitFallbackDownload 可以限制未授权(回退)连接的带宽(config.go:41-49)。

用于重新实现目的的技术分析。