How the Xray REALITY Protocol Works: TLS Fingerprint Stealing Explained
REALITY is a TLS 1.3 obfuscation layer in Xray-core that copies genuine browser fingerprints via the uTLS library while secretly authenticating peers by encrypting metadata inside the TLS Session ID using a pre-shared X25519 public key—making traffic indistinguishable from standard HTTPS.
The REALITY protocol (often called XTLS REALITY) is an advanced obfuscation transport implemented in the XTLS/Xray-core repository. It enables clients to masquerade as legitimate HTTPS traffic by "stealing" real browser TLS fingerprints while simultaneously establishing a cryptographically authenticated tunnel between client and server.
The Six-Step REALITY Handshake
The protocol operates by intercepting and modifying the standard TLS 1.3 handshake without breaking compatibility. Here is the exact sequence implemented in the source code:
1. Selecting the Browser Fingerprint
The process begins in transport/internet/reality/reality.go at lines 133-137, where the client calls tls.GetFingerprint(config.Fingerprint):
fingerprint := tls.GetFingerprint(config.Fingerprint)
The user configures this via the fingerprint field (e.g., "chrome", "firefox", or "randomized"). This instructs the uTLS library to replicate the exact ClientHello layout of that browser—including cipher suites, extensions, order, GREASE values, and padding.
2. Injecting Metadata into the Session ID
After utls.UClient creates the TLS connection, Xray rewrites the Session ID field of the ClientHello (lines 41-49 in reality.go). The 32-byte Session ID structure contains:
- Bytes 0-2: Xray version bytes
- Mid section: Current Unix timestamp
- End section: The user-defined
short-id(used for server-side routing)
The Session ID is a free-form 32-byte blob in TLS 1.3, allowing Xray to encode authentication metadata without breaking TLS format compliance.
3. Deriving the Shared Secret
Once the server responds with its ServerHello, the client performs an X25519 (or ML-KEM) Elliptic Curve Diffie-Hellman operation using the server's public key (config.PublicKey) and the KeyShare value offered by the server (lines 52-66):
sharedSecret, err := curve25519.X25519(privateKey, serverKeyShare)
The raw shared secret is then fed to HKDF-SHA256 with the client's ClientHello.random[:20] and the label "REALITY" (line 67). This produces a 32-byte AuthKey that only the client and server can compute, as the server's private key never traverses the network.
4. AEAD Encryption of Session Data
Using crypto.NewAesGcm(AuthKey), the client seals the first 16 bytes of the Session ID with the remaining portion of ClientHello.random serving as the nonce (lines 70-75). The ciphertext overwrites the original Session ID in the ClientHello.raw buffer:
aesgcm, _ := cipher.NewGCM(block)
ciphertext := aesgcm.Seal(nil, nonce, sessionID[:16], nil)
copy(sessionID[:16], ciphertext)
This step ensures that while the TLS structure matches a real browser, the payload contains encrypted authentication material.
5. Server-Side Verification
In UConn.VerifyPeerCertificate (lines 76-115 of reality.go), the server validates the client by:
- Extracting the client's public key from the leaf certificate
- Computing
HMAC-SHA512(AuthKey, pubKey) - Verifying this matches the certificate's signature
If the configuration includes Mldsa65Verify, the server additionally checks an ML-DSA-65 signature over the same HMAC. This cryptographic proof confirms the client possesses the shared secret derived from the pre-configured public key.
6. Graceful Fallback Handling
If verification fails, Xray optionally launches a spider goroutine (starting at line 86) that makes harmless HTTP requests to the destination. This provides plausible deniability by mimicking typical web browsing behavior rather than dropping the connection abruptly.
How TLS Fingerprint Stealing Works
The "fingerprint stealing" mechanism relies on the uTLS library's ability to clone real browser handshakes. Rather than generating a synthetic fingerprint, Xray passes a ClientHelloID (such as chrome) to utls.UClient, which constructs a binary-identical ClientHello to what the actual browser would send.
Critically, all REALITY modifications—including Session ID injection and AEAD encryption—occur after the ClientHello has been constructed. This means:
- Cipher suites, extensions, GREASE values, and padding match the selected browser exactly
- Passive observers (including Deep Packet Inspection systems) see only a standard TLS 1.3 handshake
- No additional protocol extensions are added that might trigger detection
The result is traffic that appears indistinguishable from the chosen browser while containing hidden authentication data accessible only to the REALITY server.
Implementation and Configuration
JSON Configuration Example
To deploy REALITY in Xray-core, configure the outbound stream settings as follows:
{
"outbounds": [
{
"protocol": "vless",
"settings": {
"vnext": [
{
"address": "example.com",
"port": 443,
"users": [{ "id": "YOUR-UUID", "encryption": "none" }]
}
]
},
"streamSettings": {
"network": "tcp",
"security": "reality",
"realitySettings": {
"publicKey": "d8f5a2e3…",
"shortId": "abcd1234",
"fingerprint": "chrome",
"serverName": "example.com"
}
}
}
]
}
The fingerprint field determines which browser signature to steal, while publicKey contains the server's X25519 public key used for the ECDH exchange.
Programmatic Implementation in Go
For developers integrating REALITY directly:
package main
import (
"context"
"log"
"time"
"github.com/xtls/xray-core/common/net"
"github.com/xtls/xray-core/transport/internet/reality"
"github.com/xtls/xray-core/transport/internet/reality/config"
)
func main() {
dest := net.ParseDestination("tcp:example.com:443")
raw, _ := net.DialTCP(dest)
cfg := &config.Config{
Fingerprint: "chrome",
ServerName: "example.com",
PublicKey: []byte{0x02, 0x5a},
ShortId: []byte{0xab, 0xcd, 0x12, 0x34},
Show: true,
}
conn, err := reality.UClient(raw, cfg, context.Background(), dest)
if err != nil {
log.Fatalf("REALITY handshake failed: %v", err)
}
defer conn.Close()
_ = conn.SetDeadline(time.Now().Add(5 * time.Second))
}
The reality.UClient function (defined in transport/internet/reality/reality.go) orchestrates the entire process: fingerprint selection, ClientHello construction, Session ID manipulation, and cryptographic verification.
Key Source Files
| File | Purpose |
|---|---|
transport/internet/reality/reality.go |
Core implementation including UClient, VerifyPeerCertificate, ECDH, HKDF, and AEAD operations |
transport/internet/reality/config.pb.go |
Protocol buffer definitions for the Config struct (public key, short-id, fingerprint) |
transport/internet/tls/tls.go |
Registry of available uTLS fingerprints (PresetFingerprints, ModernFingerprints) |
transport/internet/tcp/hub.go |
Server-side invocation of reality.Server for inbound connections (lines 115-122) |
infra/conf/transport_internet.go |
JSON/YAML configuration parsing and validation (lines 653-700) |
Summary
- REALITY is a TLS 1.3 obfuscation layer that authenticates peers while mimicking legitimate browser traffic.
- Fingerprint stealing works by copying real browser
ClientHellostructures via uTLS, then modifying only the Session ID field after construction. - Authentication uses X25519/ML-KEM ECDH to derive a shared secret, which encrypts routing metadata inside the Session ID via AES-GCM.
- Server verification checks HMAC-SHA512 signatures against the client certificate, optionally strengthened with ML-DSA-65.
- Implementation requires only a public key and fingerprint selection in Xray-core configuration files.
Frequently Asked Questions
How does REALITY steal TLS fingerprints without detection?
REALITY uses the uTLS library to replicate the exact binary layout of a real browser's ClientHello, including cipher suites, extensions, and GREASE values. Because all REALITY-specific modifications (Session ID encryption) occur after the initial ClientHello construction, passive network observers see only a standard TLS handshake that matches the selected browser signature—typically Chrome or Firefox—making the traffic indistinguishable from normal HTTPS browsing.
What encryption algorithms secure the REALITY handshake?
The protocol uses X25519 (or ML-KEM for post-quantum variants) for Elliptic Curve Diffie-Hellman key exchange, HKDF-SHA256 for key derivation using the label "REALITY", and AES-GCM for AEAD encryption of the Session ID. Server authentication optionally employs ML-DSA-65 signatures over HMAC-SHA512 digests when configured for post-quantum resistance.
Can REALITY traffic be blocked by standard DPI systems?
Standard Deep Packet Inspection systems that rely on TLS fingerprint analysis typically cannot distinguish REALITY traffic from legitimate browser connections because the outer fingerprint matches perfectly. However, active probing or sophisticated behavioral analysis might still identify patterns, though the optional spider fallback mechanism (making HTTP requests on verification failure) provides additional deniability by mimicking regular web browsing behavior.
What is the purpose of the short-id in REALITY configuration?
The short-id is an 8-byte routing identifier embedded within the encrypted Session ID (as implemented in lines 41-49 of reality.go). It allows the server to route connections to different internal services or identify specific users immediately upon decrypting the Session ID, before the full TLS handshake completes and without transmitting additional identifiable metadata in plaintext.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →