Skip to content

Commit 7e0a77c

Browse files
committed
chore: sync vless encryption code
1 parent 5f09db2 commit 7e0a77c

File tree

10 files changed

+243
-113
lines changed

10 files changed

+243
-113
lines changed

component/generater/cmd.go

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import (
1212

1313
func Main(args []string) {
1414
if len(args) < 1 {
15-
panic("Using: generate uuid/reality-keypair/wg-keypair/ech-keypair/vless-mlkem768")
15+
panic("Using: generate uuid/reality-keypair/wg-keypair/ech-keypair/vless-mlkem768/vless-x25519")
1616
}
1717
switch args[0] {
1818
case "uuid":
@@ -57,5 +57,16 @@ func Main(args []string) {
5757
}
5858
fmt.Println("Seed: " + seedBase64)
5959
fmt.Println("Client: " + clientBase64)
60+
case "vless-x25519":
61+
var privateKey string
62+
if len(args) > 1 {
63+
privateKey = args[1]
64+
}
65+
privateKeyBase64, passwordBase64, err := encryption.GenX25519(privateKey)
66+
if err != nil {
67+
panic(err)
68+
}
69+
fmt.Println("PrivateKey:" + privateKeyBase64)
70+
fmt.Println("Password:" + passwordBase64)
6071
}
6172
}

docs/config.yaml

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -638,8 +638,12 @@ proxies: # socks5
638638
port: 443
639639
uuid: uuid
640640
network: tcp
641-
encryption: "8min-vless-mlkem768client-bas64RawURLEncoding" # 复用八分钟后协商新的 sharedKey,需小于服务端的值
642-
# encryption: "8min-xored-mlkem768client-bas64RawURLEncoding"
641+
# -------------------------
642+
# vless encryption客户端配置:
643+
# (只使用 1-RTT 模式 / 复用八分钟后协商新的 baseKey,周期需小于服务端的值)
644+
# / 是只能选一个,后面是 base64RawURLEncoding,使用 mihomo generate vless-x25519 和 mihomo generate vless-mlkem768 生成,替换值时需去掉括号
645+
# -------------------------
646+
encryption: "1rtt/8min.native/divide/random.mlkem768Client.(X25519 Password).(ML-KEM-768 Client)"
643647
tls: false #可以不开启tls
644648
udp: true
645649

@@ -1359,8 +1363,12 @@ listeners:
13591363
flow: xtls-rprx-vision
13601364
# ws-path: "/" # 如果不为空则开启 websocket 传输层
13611365
# grpc-service-name: "GunService" # 如果不为空则开启 grpc 传输层
1362-
# decryption: "10min-vless-mlkem768seed-bas64RawURLEncoding" # 同时允许 1-RTT 模式与十分钟复用的 0-RTT 模式, 后面base64字符串可由可由 mihomo generate vless-mlkem768 命令生成
1363-
# decryption: "10min-xored-mlkem768seed-bas64RawURLEncoding"
1366+
# -------------------------
1367+
# vless encryption服务端配置:
1368+
# (只允许 1-RTT 模式 / 同时允许 1-RTT 模式与十分钟复用的 0-RTT 模式;原生外观 / ECH 式 XOR / 全随机数)
1369+
# / 是只能选一个,后面是 base64RawURLEncoding,使用 mihomo generate vless-x25519 和 mihomo generate vless-mlkem768 生成,替换值时需去掉括号
1370+
# -------------------------
1371+
# decryption: "1rtt/10min.native/divide/random.mlkem768Seed.(X25519 PrivateKey).(ML-KEM-768 Seed)"
13641372
# 下面两项如果填写则开启 tls(需要同时填写)
13651373
# certificate: ./server.crt
13661374
# private-key: ./server.key

listener/inbound/vless_test.go

Lines changed: 25 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -94,34 +94,33 @@ func TestInboundVless_Encryption(t *testing.T) {
9494
t.Fatal(err)
9595
return
9696
}
97-
t.Run("-vless-", func(t *testing.T) {
98-
inboundOptions := inbound.VlessOption{
99-
Decryption: "10min-vless-mlkem768seed-" + seedBase64,
100-
}
101-
outboundOptions := outbound.VlessOption{
102-
Encryption: "8min-vless-mlkem768client-" + clientBase64,
103-
}
104-
testInboundVless(t, inboundOptions, outboundOptions)
105-
t.Run("xtls-rprx-vision", func(t *testing.T) {
106-
outboundOptions := outboundOptions
107-
outboundOptions.Flow = "xtls-rprx-vision"
108-
testInboundVless(t, inboundOptions, outboundOptions)
109-
})
110-
})
111-
t.Run("-xored-", func(t *testing.T) {
112-
inboundOptions := inbound.VlessOption{
113-
Decryption: "10min-xored-mlkem768seed-" + seedBase64,
114-
}
115-
outboundOptions := outbound.VlessOption{
116-
Encryption: "8min-xored-mlkem768client-" + clientBase64,
117-
}
118-
testInboundVless(t, inboundOptions, outboundOptions)
119-
t.Run("xtls-rprx-vision", func(t *testing.T) {
120-
outboundOptions := outboundOptions
121-
outboundOptions.Flow = "xtls-rprx-vision"
97+
privateKeyBase64, passwordBase64, err := encryption.GenX25519("")
98+
if err != nil {
99+
t.Fatal(err)
100+
return
101+
}
102+
var modes = []string{
103+
"native",
104+
"divide",
105+
"random",
106+
}
107+
for i := range modes {
108+
mode := modes[i]
109+
t.Run(mode, func(t *testing.T) {
110+
inboundOptions := inbound.VlessOption{
111+
Decryption: "10min." + mode + ".mlkem768Seed." + privateKeyBase64 + "." + seedBase64,
112+
}
113+
outboundOptions := outbound.VlessOption{
114+
Encryption: "8min." + mode + ".mlkem768Client." + passwordBase64 + "." + clientBase64,
115+
}
122116
testInboundVless(t, inboundOptions, outboundOptions)
117+
t.Run("xtls-rprx-vision", func(t *testing.T) {
118+
outboundOptions := outboundOptions
119+
outboundOptions.Flow = "xtls-rprx-vision"
120+
testInboundVless(t, inboundOptions, outboundOptions)
121+
})
123122
})
124-
})
123+
}
125124
}
126125

127126
func TestInboundVless_Wss1(t *testing.T) {

transport/vless/encryption/client.go

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package encryption
33
import (
44
"bytes"
55
"crypto/cipher"
6+
"crypto/ecdh"
67
"crypto/rand"
78
"errors"
89
"fmt"
@@ -40,7 +41,8 @@ type ClientInstance struct {
4041
sync.RWMutex
4142
nfsEKey *mlkem.EncapsulationKey768
4243
hash11 [11]byte // no more capacity
43-
xorKey []byte
44+
xorMode uint32
45+
xorPKey *ecdh.PublicKey
4446
minutes time.Duration
4547
expire time.Time
4648
baseKey []byte
@@ -60,31 +62,32 @@ type ClientConn struct {
6062
input bytes.Reader // peerCache
6163
}
6264

63-
func (i *ClientInstance) Init(nfsEKeyBytes []byte, xor uint32, minutes time.Duration) (err error) {
65+
func (i *ClientInstance) Init(nfsEKeyBytes, xorPKeyBytes []byte, xorMode, minutes uint32) (err error) {
6466
if i.nfsEKey != nil {
6567
err = errors.New("already initialized")
6668
return
6769
}
68-
i.nfsEKey, err = mlkem.NewEncapsulationKey768(nfsEKeyBytes)
69-
if err != nil {
70+
if i.nfsEKey, err = mlkem.NewEncapsulationKey768(nfsEKeyBytes); err != nil {
7071
return
7172
}
7273
hash32 := sha3.Sum256(nfsEKeyBytes)
7374
copy(i.hash11[:], hash32[:])
74-
if xor > 0 {
75-
xorKey := sha3.Sum256(nfsEKeyBytes)
76-
i.xorKey = xorKey[:]
75+
if xorMode > 0 {
76+
i.xorMode = xorMode
77+
if i.xorPKey, err = ecdh.X25519().NewPublicKey(xorPKeyBytes); err != nil {
78+
return
79+
}
7780
}
78-
i.minutes = minutes
81+
i.minutes = time.Duration(minutes) * time.Minute
7982
return
8083
}
8184

8285
func (i *ClientInstance) Handshake(conn net.Conn) (*ClientConn, error) {
8386
if i.nfsEKey == nil {
8487
return nil, errors.New("uninitialized")
8588
}
86-
if i.xorKey != nil {
87-
conn = NewXorConn(conn, i.xorKey)
89+
if i.xorMode > 0 {
90+
conn, _ = NewXorConn(conn, i.xorMode, i.xorPKey, nil)
8891
}
8992
c := &ClientConn{Conn: conn}
9093

@@ -145,7 +148,7 @@ func (i *ClientInstance) Handshake(conn net.Conn) (*ClientConn, error) {
145148
}
146149
c.baseKey = append(pfsKey, nfsKey...)
147150

148-
VLESS, _ := NewAead(ClientCipher, c.baseKey, encapsulatedPfsKey, encapsulatedNfsKey).Open(nil, append(i.hash11[:], ClientCipher), c.ticket[11:], pfsEKeyBytes)
151+
VLESS, _ := NewAEAD(ClientCipher, c.baseKey, encapsulatedPfsKey, encapsulatedNfsKey).Open(nil, append(i.hash11[:], ClientCipher), c.ticket[11:], pfsEKeyBytes)
149152
if !bytes.Equal(VLESS, []byte("VLESS")) {
150153
return nil, errors.New("invalid server")
151154
}
@@ -180,15 +183,15 @@ func (c *ClientConn) Write(b []byte) (int, error) {
180183
rand.Read(c.random)
181184
copy(data[5+32:], c.random)
182185
EncodeHeader(data[5+32+32:], 23, len(b)+16)
183-
c.aead = NewAead(ClientCipher, c.baseKey, c.random, c.ticket)
186+
c.aead = NewAEAD(ClientCipher, c.baseKey, c.random, c.ticket)
184187
c.nonce = make([]byte, 12)
185188
c.aead.Seal(data[:5+32+32+5], c.nonce, b, data[5+32+32:5+32+32+5])
186189
} else {
187190
data = make([]byte, 5+len(b)+16)
188191
EncodeHeader(data, 23, len(b)+16)
189192
c.aead.Seal(data[:5], c.nonce, b, data[:5])
190193
if bytes.Equal(c.nonce, MaxNonce) {
191-
c.aead = NewAead(ClientCipher, c.baseKey, data[5:], data[:5])
194+
c.aead = NewAEAD(ClientCipher, c.baseKey, data[5:], data[:5])
192195
}
193196
}
194197
IncreaseNonce(c.nonce)
@@ -229,7 +232,7 @@ func (c *ClientConn) Read(b []byte) (int, error) {
229232
if c.random == nil {
230233
return 0, errors.New("empty c.random")
231234
}
232-
c.peerAead = NewAead(ClientCipher, c.baseKey, peerRandomHello, c.random)
235+
c.peerAead = NewAEAD(ClientCipher, c.baseKey, peerRandomHello, c.random)
233236
c.peerNonce = make([]byte, 12)
234237
}
235238
if c.input.Len() > 0 {
@@ -252,7 +255,7 @@ func (c *ClientConn) Read(b []byte) (int, error) {
252255
}
253256
var peerAead cipher.AEAD
254257
if bytes.Equal(c.peerNonce, MaxNonce) {
255-
peerAead = NewAead(ClientCipher, c.baseKey, peerData, h)
258+
peerAead = NewAEAD(ClientCipher, c.baseKey, peerData, h)
256259
}
257260
_, err = c.peerAead.Open(dst[:0], c.peerNonce, peerData, h)
258261
if peerAead != nil {

transport/vless/encryption/common.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ func ReadAndDiscardPaddings(conn net.Conn) (h []byte, t byte, l int, err error)
7373
}
7474
}
7575

76-
func NewAead(c byte, secret, salt, info []byte) (aead cipher.AEAD) {
76+
func NewAEAD(c byte, secret, salt, info []byte) (aead cipher.AEAD) {
7777
key := make([]byte, 32)
7878
hkdf.New(sha3.New256, secret, salt, info).Read(key)
7979
if c&1 == 1 {

transport/vless/encryption/doc.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,5 @@
1414
// https://github.com/XTLS/Xray-core/commit/d1fb48521271251a8c74bd64fcc2fc8700717a3b
1515
// https://github.com/XTLS/Xray-core/commit/49580705f6029648399304b816a2737f991582a8
1616
// https://github.com/XTLS/Xray-core/commit/84835bec7d0d8555d0dd30953ed26a272de814c4
17+
// https://github.com/XTLS/Xray-core/commit/373558ed7abdbac3de41745cf30ec04c9adde604
1718
package encryption

transport/vless/encryption/factory.go

Lines changed: 42 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import (
55
"fmt"
66
"strconv"
77
"strings"
8-
"time"
98
)
109

1110
// NewClient new client from encryption string
@@ -15,7 +14,7 @@ func NewClient(encryption string) (*ClientInstance, error) {
1514
case "", "none": // We will not reject empty string like xray-core does, because we need to ensure compatibility
1615
return nil, nil
1716
}
18-
if s := strings.SplitN(encryption, "-", 4); len(s) == 4 && s[2] == "mlkem768client" {
17+
if s := strings.Split(encryption, "."); len(s) == 5 && s[2] == "mlkem768Client" {
1918
var minutes uint32
2019
if s[0] != "1rtt" {
2120
t := strings.TrimSuffix(s[0], "min")
@@ -28,27 +27,35 @@ func NewClient(encryption string) (*ClientInstance, error) {
2827
}
2928
minutes = uint32(i)
3029
}
31-
var xor uint32
30+
var xorMode uint32
3231
switch s[1] {
33-
case "vless":
34-
case "xored":
35-
xor = 1
32+
case "native":
33+
case "divide":
34+
xorMode = 1
35+
case "random":
36+
xorMode = 2
3637
default:
3738
return nil, fmt.Errorf("invaild vless encryption value: %s", encryption)
3839
}
39-
b, err := base64.RawURLEncoding.DecodeString(s[3])
40+
xorPKeyBytes, err := base64.RawURLEncoding.DecodeString(s[3])
4041
if err != nil {
4142
return nil, fmt.Errorf("invaild vless encryption value: %s", encryption)
4243
}
43-
if len(b) == MLKEM768ClientLength {
44-
client := &ClientInstance{}
45-
if err = client.Init(b, xor, time.Duration(minutes)*time.Minute); err != nil {
46-
return nil, fmt.Errorf("failed to use mlkem768seed: %w", err)
47-
}
48-
return client, nil
49-
} else {
44+
if len(xorPKeyBytes) != X25519PasswordSize {
45+
return nil, fmt.Errorf("invaild vless encryption value: %s", encryption)
46+
}
47+
nfsEKeyBytes, err := base64.RawURLEncoding.DecodeString(s[4])
48+
if err != nil {
5049
return nil, fmt.Errorf("invaild vless encryption value: %s", encryption)
5150
}
51+
if len(nfsEKeyBytes) != MLKEM768ClientLength {
52+
return nil, fmt.Errorf("invaild vless encryption value: %s", encryption)
53+
}
54+
client := &ClientInstance{}
55+
if err = client.Init(nfsEKeyBytes, xorPKeyBytes, xorMode, minutes); err != nil {
56+
return nil, fmt.Errorf("failed to use mlkem768seed: %w", err)
57+
}
58+
return client, nil
5259
}
5360
return nil, fmt.Errorf("invaild vless encryption value: %s", encryption)
5461
}
@@ -60,7 +67,7 @@ func NewServer(decryption string) (*ServerInstance, error) {
6067
case "", "none": // We will not reject empty string like xray-core does, because we need to ensure compatibility
6168
return nil, nil
6269
}
63-
if s := strings.SplitN(decryption, "-", 4); len(s) == 4 && s[2] == "mlkem768seed" {
70+
if s := strings.Split(decryption, "."); len(s) == 5 && s[2] == "mlkem768Seed" {
6471
var minutes uint32
6572
if s[0] != "1rtt" {
6673
t := strings.TrimSuffix(s[0], "min")
@@ -73,27 +80,35 @@ func NewServer(decryption string) (*ServerInstance, error) {
7380
}
7481
minutes = uint32(i)
7582
}
76-
var xor uint32
83+
var xorMode uint32
7784
switch s[1] {
78-
case "vless":
79-
case "xored":
80-
xor = 1
85+
case "native":
86+
case "divide":
87+
xorMode = 1
88+
case "random":
89+
xorMode = 2
8190
default:
8291
return nil, fmt.Errorf("invaild vless decryption value: %s", decryption)
8392
}
84-
b, err := base64.RawURLEncoding.DecodeString(s[3])
93+
xorSKeyBytes, err := base64.RawURLEncoding.DecodeString(s[3])
8594
if err != nil {
8695
return nil, fmt.Errorf("invaild vless decryption value: %s", decryption)
8796
}
88-
if len(b) == MLKEM768SeedLength {
89-
server := &ServerInstance{}
90-
if err = server.Init(b, xor, time.Duration(minutes)*time.Minute); err != nil {
91-
return nil, fmt.Errorf("failed to use mlkem768seed: %w", err)
92-
}
93-
return server, nil
94-
} else {
97+
if len(xorSKeyBytes) != X25519PrivateKeySize {
98+
return nil, fmt.Errorf("invaild vless decryption value: %s", decryption)
99+
}
100+
nfsDKeySeed, err := base64.RawURLEncoding.DecodeString(s[4])
101+
if err != nil {
95102
return nil, fmt.Errorf("invaild vless decryption value: %s", decryption)
96103
}
104+
if len(nfsDKeySeed) != MLKEM768SeedLength {
105+
return nil, fmt.Errorf("invaild vless decryption value: %s", decryption)
106+
}
107+
server := &ServerInstance{}
108+
if err = server.Init(nfsDKeySeed, xorSKeyBytes, xorMode, minutes); err != nil {
109+
return nil, fmt.Errorf("failed to use mlkem768seed: %w", err)
110+
}
111+
return server, nil
97112
}
98113
return nil, fmt.Errorf("invaild vless decryption value: %s", decryption)
99114
}

0 commit comments

Comments
 (0)