From 49580705f6029648399304b816a2737f991582a8 Mon Sep 17 00:00:00 2001 From: RPRX <63339210+RPRX@users.noreply.github.com> Date: Mon, 18 Aug 2025 14:58:21 +0000 Subject: [PATCH] Use SHA3-256 instead of SHA2-256; Support XTLS Vision for random appearance https://github.com/XTLS/Xray-core/pull/4952#issuecomment-3197163816 --- infra/conf/vless.go | 10 +-- proxy/proxy.go | 4 + proxy/vless/encryption/client.go | 6 +- proxy/vless/encryption/common.go | 7 +- proxy/vless/encryption/server.go | 8 +- proxy/vless/encryption/xor.go | 138 ++++++++++++++++++++++--------- proxy/vless/inbound/inbound.go | 16 ++-- proxy/vless/outbound/outbound.go | 14 ++-- 8 files changed, 129 insertions(+), 74 deletions(-) diff --git a/infra/conf/vless.go b/infra/conf/vless.go index 34288d16..5fcdf34c 100644 --- a/infra/conf/vless.go +++ b/infra/conf/vless.go @@ -61,10 +61,6 @@ func (c *VLessInboundConfig) Build() (proto.Message, error) { return nil, errors.New(`VLESS clients: "flow" doesn't support "` + account.Flow + `" in this version`) } - if strings.Contains(c.Decryption, "xored") && account.Flow == vless.XRV { - return nil, errors.New(`VLESS clients: "xored" doesn't support "flow" yet`) - } - if account.Encryption != "" { return nil, errors.New(`VLESS clients: "encryption" should not in inbound settings`) } @@ -213,11 +209,7 @@ func (c *VLessOutboundConfig) Build() (proto.Message, error) { account.Id = u.String() switch account.Flow { - case "": - case vless.XRV, vless.XRV + "-udp443": - if strings.Contains(account.Encryption, "xored") { - return nil, errors.New(`VLESS users: "xored" doesn't support "flow" yet`) - } + case "", vless.XRV, vless.XRV + "-udp443": default: return nil, errors.New(`VLESS users: "flow" doesn't support "` + account.Flow + `" in this version`) } diff --git a/proxy/proxy.go b/proxy/proxy.go index 3fec31af..38f38b41 100644 --- a/proxy/proxy.go +++ b/proxy/proxy.go @@ -25,6 +25,7 @@ import ( "github.com/xtls/xray-core/common/signal" "github.com/xtls/xray-core/features/routing" "github.com/xtls/xray-core/features/stats" + "github.com/xtls/xray-core/proxy/vless/encryption" "github.com/xtls/xray-core/transport" "github.com/xtls/xray-core/transport/internet" "github.com/xtls/xray-core/transport/internet/reality" @@ -534,6 +535,9 @@ func UnwrapRawConn(conn net.Conn) (net.Conn, stats.Counter, stats.Counter) { readCounter = statConn.ReadCounter writerCounter = statConn.WriteCounter } + if _, ok := conn.(*encryption.XorConn); ok { + return conn, readCounter, writerCounter + } if xc, ok := conn.(*tls.Conn); ok { conn = xc.NetConn() } else if utlsConn, ok := conn.(*tls.UConn); ok { diff --git a/proxy/vless/encryption/client.go b/proxy/vless/encryption/client.go index d0d4b3d5..5092de5f 100644 --- a/proxy/vless/encryption/client.go +++ b/proxy/vless/encryption/client.go @@ -5,7 +5,7 @@ import ( "crypto/cipher" "crypto/mlkem" "crypto/rand" - "crypto/sha256" + "crypto/sha3" "io" "net" "strings" @@ -58,10 +58,10 @@ func (i *ClientInstance) Init(nfsEKeyBytes []byte, xor uint32, minutes time.Dura if err != nil { return } - hash256 := sha256.Sum256(nfsEKeyBytes) + hash256 := sha3.Sum256(nfsEKeyBytes) copy(i.hash11[:], hash256[:]) if xor > 0 { - xorKey := sha256.Sum256(nfsEKeyBytes) + xorKey := sha3.Sum256(nfsEKeyBytes) i.xorKey = xorKey[:] } i.minutes = minutes diff --git a/proxy/vless/encryption/common.go b/proxy/vless/encryption/common.go index 58c096e3..d4484bd6 100644 --- a/proxy/vless/encryption/common.go +++ b/proxy/vless/encryption/common.go @@ -4,14 +4,14 @@ import ( "bytes" "crypto/aes" "crypto/cipher" - "crypto/sha256" + "crypto/hkdf" + "crypto/sha3" "fmt" "io" "net" "github.com/xtls/xray-core/common/errors" "golang.org/x/crypto/chacha20poly1305" - "golang.org/x/crypto/hkdf" ) var MaxNonce = bytes.Repeat([]byte{255}, 12) @@ -73,8 +73,7 @@ func ReadAndDiscardPaddings(conn net.Conn) (h []byte, t byte, l int, err error) } func NewAead(c byte, secret, salt, info []byte) (aead cipher.AEAD) { - key := make([]byte, 32) - hkdf.New(sha256.New, secret, salt, info).Read(key) + key, _ := hkdf.Key(sha3.New256, secret, salt, string(info), 32) if c&1 == 1 { block, _ := aes.NewCipher(key) aead, _ = cipher.NewGCM(block) diff --git a/proxy/vless/encryption/server.go b/proxy/vless/encryption/server.go index 5ea1f0d6..c1d99611 100644 --- a/proxy/vless/encryption/server.go +++ b/proxy/vless/encryption/server.go @@ -5,7 +5,7 @@ import ( "crypto/cipher" "crypto/mlkem" "crypto/rand" - "crypto/sha256" + "crypto/sha3" "fmt" "io" "net" @@ -55,10 +55,10 @@ func (i *ServerInstance) Init(nfsDKeySeed []byte, xor uint32, minutes time.Durat if err != nil { return } - hash256 := sha256.Sum256(i.nfsDKey.EncapsulationKey().Bytes()) + hash256 := sha3.Sum256(i.nfsDKey.EncapsulationKey().Bytes()) copy(i.hash11[:], hash256[:]) if xor > 0 { - xorKey := sha256.Sum256(i.nfsDKey.EncapsulationKey().Bytes()) + xorKey := sha3.Sum256(i.nfsDKey.EncapsulationKey().Bytes()) i.xorKey = xorKey[:] } if minutes > 0 { @@ -282,9 +282,9 @@ func (c *ServerConn) Write(b []byte) (int, error) { data = make([]byte, 5+32+5+len(b)+16) EncodeHeader(data, 0, 32) rand.Read(data[5 : 5+32]) + EncodeHeader(data[5+32:], 23, len(b)+16) c.aead = NewAead(c.cipher, c.baseKey, data[5:5+32], c.peerRandom) c.nonce = make([]byte, 12) - EncodeHeader(data[5+32:], 23, len(b)+16) c.aead.Seal(data[:5+32+5], c.nonce, b, data[5+32:5+32+5]) } else { data = make([]byte, 5+len(b)+16) diff --git a/proxy/vless/encryption/xor.go b/proxy/vless/encryption/xor.go index 64828eaa..69ff1578 100644 --- a/proxy/vless/encryption/xor.go +++ b/proxy/vless/encryption/xor.go @@ -15,6 +15,14 @@ type XorConn struct { peerCtr cipher.Stream isHeader bool skipNext bool + + out_after0 bool + out_header []byte + out_skip int + + in_after0 bool + in_header []byte + in_skip int } func NewXorConn(conn net.Conn, key []byte) *XorConn { @@ -26,29 +34,56 @@ func (c *XorConn) Write(b []byte) (int, error) { // whole one/two records if len(b) == 0 { return 0, nil } - var iv []byte - if c.ctr == nil { - block, _ := aes.NewCipher(c.key) - iv = make([]byte, 16) - rand.Read(iv) - c.ctr = cipher.NewCTR(block, iv) + if !c.out_after0 { + var iv []byte + if c.ctr == nil { + block, _ := aes.NewCipher(c.key) + iv = make([]byte, 16) + rand.Read(iv) + c.ctr = cipher.NewCTR(block, iv) + } + t, l, _ := DecodeHeader(b) + if t == 23 { // single 23 + l = 5 + } else { // 1/0 + 23, or noises only + l += 10 + if t == 0 { + c.out_after0 = true + } + } + c.ctr.XORKeyStream(b[:l], b[:l]) // caller MUST discard b + if iv != nil { + b = append(iv, b...) + } + if _, err := c.Conn.Write(b); err != nil { + return 0, err + } + if iv != nil { + b = b[16:] // for len(b) + } + return len(b), nil } - t, l, _ := DecodeHeader(b) - if t == 23 { // single 23 - l = 5 - } else { // 1/0 + 23, or noises only - l += 10 - } - c.ctr.XORKeyStream(b[:l], b[:l]) // caller MUST discard b - if iv != nil { - b = append(iv, b...) + for p := b; ; { // for XTLS + if len(p) <= c.out_skip { + c.out_skip -= len(p) + break + } + p = p[c.out_skip:] + c.out_skip = 0 + need := 5 - len(c.out_header) + if len(p) < need { + c.out_header = append(c.out_header, p...) + c.ctr.XORKeyStream(p, p) + break + } + _, c.out_skip, _ = DecodeHeader(append(c.out_header, p[:need]...)) + c.out_header = make([]byte, 0, 5) // DO NOT CHANGE + c.ctr.XORKeyStream(p[:need], p[:need]) + p = p[need:] } if _, err := c.Conn.Write(b); err != nil { return 0, err } - if iv != nil { - b = b[16:] // for len(b) - } return len(b), nil } @@ -56,31 +91,56 @@ func (c *XorConn) Read(b []byte) (int, error) { // 5-bytes, data, 5-bytes... if len(b) == 0 { return 0, nil } - if c.peerCtr == nil { - peerIv := make([]byte, 16) - if _, err := io.ReadFull(c.Conn, peerIv); err != nil { + if !c.in_after0 || !c.isHeader { + if c.peerCtr == nil { + peerIv := make([]byte, 16) + if _, err := io.ReadFull(c.Conn, peerIv); err != nil { + return 0, err + } + block, _ := aes.NewCipher(c.key) + c.peerCtr = cipher.NewCTR(block, peerIv) + c.isHeader = true + } + if _, err := io.ReadFull(c.Conn, b); err != nil { return 0, err } - block, _ := aes.NewCipher(c.key) - c.peerCtr = cipher.NewCTR(block, peerIv) - c.isHeader = true - } - if _, err := io.ReadFull(c.Conn, b); err != nil { - return 0, err - } - if c.skipNext { - c.skipNext = false + if c.skipNext { + c.skipNext = false + return len(b), nil + } + c.peerCtr.XORKeyStream(b, b) + if c.isHeader { // always 5-bytes + if t, _, _ := DecodeHeader(b); t == 23 { + c.skipNext = true + } else { + c.isHeader = false + if t == 0 { + c.in_after0 = true + } + } + } else { + c.isHeader = true + } return len(b), nil } - c.peerCtr.XORKeyStream(b, b) - if c.isHeader { // always 5-bytes - if t, _, _ := DecodeHeader(b); t == 23 { - c.skipNext = true - } else { - c.isHeader = false + n, err := c.Conn.Read(b) + for p := b[:n]; ; { // for XTLS + if len(p) <= c.in_skip { + c.in_skip -= len(p) + break } - } else { - c.isHeader = true + p = p[c.in_skip:] + c.in_skip = 0 + need := 5 - len(c.in_header) + if len(p) < need { + c.peerCtr.XORKeyStream(p, p) + c.in_header = append(c.in_header, p...) + break + } + c.peerCtr.XORKeyStream(p[:need], p[:need]) + _, c.in_skip, _ = DecodeHeader(append(c.in_header, p[:need]...)) + c.in_header = make([]byte, 0, 5) // DO NOT CHANGE + p = p[need:] } - return len(b), nil + return n, err } diff --git a/proxy/vless/inbound/inbound.go b/proxy/vless/inbound/inbound.go index deaf1cf1..652dadd2 100644 --- a/proxy/vless/inbound/inbound.go +++ b/proxy/vless/inbound/inbound.go @@ -208,6 +208,14 @@ func (*Handler) Network() []net.Network { // Process implements proxy.Inbound.Process(). func (h *Handler) Process(ctx context.Context, network net.Network, connection stat.Connection, dispatcher routing.Dispatcher) error { + if h.decryption != nil { + var err error + connection, err = h.decryption.Handshake(connection) + if err != nil { + return errors.New("ML-KEM-768 handshake failed").Base(err).AtInfo() + } + } + iConn := connection if statConn, ok := iConn.(*stat.CounterConnection); ok { iConn = statConn.Connection @@ -218,14 +226,6 @@ func (h *Handler) Process(ctx context.Context, network net.Network, connection s return errors.New("unable to set read deadline").Base(err).AtWarning() } - if h.decryption != nil { - var err error - connection, err = h.decryption.Handshake(connection) - if err != nil { - return errors.New("ML-KEM-768 handshake failed").Base(err).AtInfo() - } - } - first := buf.FromBytes(make([]byte, buf.Size)) first.Clear() firstLen, errR := first.ReadFrom(connection) diff --git a/proxy/vless/outbound/outbound.go b/proxy/vless/outbound/outbound.go index 9be88039..dd777314 100644 --- a/proxy/vless/outbound/outbound.go +++ b/proxy/vless/outbound/outbound.go @@ -103,13 +103,6 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte } defer conn.Close() - iConn := conn - if statConn, ok := iConn.(*stat.CounterConnection); ok { - iConn = statConn.Connection - } - target := ob.Target - errors.LogInfo(ctx, "tunneling request to ", target, " via ", rec.Destination().NetAddr()) - if h.encryption != nil { var err error conn, err = h.encryption.Handshake(conn) @@ -118,6 +111,13 @@ func (h *Handler) Process(ctx context.Context, link *transport.Link, dialer inte } } + iConn := conn + if statConn, ok := iConn.(*stat.CounterConnection); ok { + iConn = statConn.Connection + } + target := ob.Target + errors.LogInfo(ctx, "tunneling request to ", target, " via ", rec.Destination().NetAddr()) + command := protocol.RequestCommandTCP if target.Network == net.Network_UDP { command = protocol.RequestCommandUDP