بروتوكول Shadowsocks
يُنفّذ Xray-core جيلين من Shadowsocks: تشفيرات AEAD الكلاسيكية (Shadowsocks الأصلي) في proxy/shadowsocks/، وبروتوكول Shadowsocks 2022 الحديث (عبر مكتبة sing-shadowsocks) في proxy/shadowsocks_2022/.
نظرة عامة
| الميزة | SS الكلاسيكي | Shadowsocks 2022 |
|---|---|---|
| وارد | نعم | نعم (مستخدم واحد + متعدد المستخدمين + ترحيل) |
| صادر | نعم | نعم |
| TCP | نعم | نعم |
| UDP | نعم | نعم |
| التشفير | AES-128/256-GCM, ChaCha20-Poly1305, XChaCha20-Poly1305, بدون | 2022-blake3-aes-128/256-gcm, 2022-blake3-chacha20-poly1305 |
| تعدد المستخدمين | نعم (AEAD فقط) | نعم (بمفاتيح PSK منفصلة) |
| حماية إعادة التشغيل | تصريف بذرة السلوك | مُدمجة (مكتبة sing) |
| صيغة المفتاح | كلمة مرور (اشتقاق MD5) | مفتاح مُشترك مسبقاً بترميز base64 |
Shadowsocks الكلاسيكي
صيغة السلك — تدفق TCP
+---------+-------------------+-------------------+
| [IV] | Encrypted Header | Encrypted Payload |
| 0-32B | (chunked AEAD) | (chunked AEAD) |
+---------+-------------------+-------------------+الترويسة (داخل أول جزء مشفر):
+----------+----------+------+
| AddrType | Address | Port |
| 1 byte | variable | 2B |
+----------+----------+------+المصدر: proxy/shadowsocks/protocol.go:57-131
أنواع العناوين:
| البايت (البتات السفلى 4) | النوع |
|---|---|
0x01 | IPv4 (4 بايت) |
0x03 | نطاق (1 بايت للطول + سلسلة نصية) |
0x04 | IPv6 (16 بايت) |
ملاحظة: يستخدم محلل العناوين محللاً مخصصاً يُقنّع البتات العليا 4: b & 0x0F
المصدر: proxy/shadowsocks/protocol.go:24-31
صيغة السلك — حزمة UDP
كل حزمة UDP تُشفّر بشكل مستقل:
+---------+----------+----------+------+---------+----------+
| IV | AddrType | Address | Port | Payload | Auth Tag |
| 0-32B | 1B | variable | 2B | ... | 16B |
+---------+----------+----------+------+---------+----------+المصدر: proxy/shadowsocks/protocol.go:207-228
تنفيذات التشفير
تشفيرات AEAD
جميع تشفيرات AEAD تتبع النمط ذاته:
- IV: بايتات عشوائية (بحجم خاص بالتشفير) تُضاف في بداية التدفق
- اشتقاق المفتاح الفرعي: HKDF-SHA1 مع IV كملح:
HKDF(key, iv, "ss-subkey") -> subkey - أجزاء مُصادقة: كل جزء هو
[encrypted_length(2B + 16B tag)] [encrypted_payload(N + 16B tag)] - Nonce: عداد متزايد تلقائياً
func (c *AEADCipher) createAuthenticator(key, iv []byte) *crypto.AEADAuthenticator {
subkey := make([]byte, c.KeyBytes)
hkdfSHA1(key, iv, subkey)
aead := c.AEADAuthCreator(subkey)
nonce := crypto.GenerateAEADNonceWithSize(aead.NonceSize())
return &crypto.AEADAuthenticator{
AEAD: aead,
NonceGenerator: nonce,
}
}المصدر: proxy/shadowsocks/config.go:138-147
| التشفير | حجم المفتاح | حجم IV | AEAD |
|---|---|---|---|
AES_128_GCM | 16 | 16 | AES-128-GCM |
AES_256_GCM | 32 | 32 | AES-256-GCM |
CHACHA20_POLY1305 | 32 | 32 | ChaCha20-Poly1305 |
XCHACHA20_POLY1305 | 32 | 32 | XChaCha20-Poly1305 |
NONE | 0 | 0 | بدون (نص عادي) |
المصدر: proxy/shadowsocks/config.go:62-93
اشتقاق المفتاح من كلمة المرور
يشتق Shadowsocks الكلاسيكي مفاتيح التشفير من كلمات المرور باستخدام MD5 المتكرر:
func passwordToCipherKey(password []byte, keySize int32) []byte {
key := make([]byte, 0, keySize)
md5Sum := md5.Sum(password)
key = append(key, md5Sum[:]...)
for int32(len(key)) < keySize {
md5Hash := md5.New()
md5Hash.Write(md5Sum[:])
md5Hash.Write(password)
md5Hash.Sum(md5Sum[:0])
key = append(key, md5Sum[:]...)
}
return key
}المصدر: proxy/shadowsocks/config.go:213-228
اشتقاق المفتاح الفرعي بـ HKDF
func hkdfSHA1(secret, salt, outKey []byte) {
r := hkdf.New(sha1.New, secret, salt, []byte("ss-subkey"))
io.ReadFull(r, outKey)
}المصدر: proxy/shadowsocks/config.go:230-233
دعم تعدد المستخدمين
يتكرر Validator على جميع المستخدمين المسجلين، محاولاً فك تشفير AEAD بمفتاح كل مستخدم:
func (v *Validator) Get(bs []byte, command RequestCommand) (...) {
for _, user := range v.users {
account := user.Account.(*MemoryAccount)
if account.Cipher.IsAEAD() {
aeadCipher := account.Cipher.(*AEADCipher)
iv := bs[:ivLen]
subkey := hkdfSHA1(account.Key, iv, ...)
aead := aeadCipher.AEADAuthCreator(subkey)
// Try to decrypt first chunk
ret, matchErr = aead.Open(data[:0], nonce, bs[ivLen:ivLen+18], nil)
if matchErr == nil { return user }
}
}
}المصدر: proxy/shadowsocks/validator.go:112-154
القيد: تشفيرات غير AEAD (None) تدعم مستخدماً واحداً فقط لأنه لا يوجد وسم مصادقة للمطابقة.
المصدر: proxy/shadowsocks/validator.go:33-35
تصريف بذرة السلوك
مثل VMess، يستخدم Shadowsocks مُصرّفاً حتمياً لقراءة كميات عشوائية من البيانات قبل إغلاق الاتصالات غير الصالحة، مما يمنع الاستقصاء:
hashkdf := hmac.New(sha256.New, []byte("SSBSKDF"))
hashkdf.Write(account.Key)
behaviorSeed = crc64.Update(behaviorSeed, crc64.MakeTable(crc64.ECMA), hashkdf.Sum(nil))المصدر: proxy/shadowsocks/validator.go:39-41
Shadowsocks 2022
Shadowsocks 2022 هو إعادة تصميم كاملة بخصائص أمنية أفضل. يفوّض Xray-core تنفيذ البروتوكول إلى مكتبة sing-shadowsocks (github.com/sagernet/sing-shadowsocks).
الاختلافات الرئيسية عن الإصدار الكلاسيكي
- مفاتيح مُشتركة مسبقاً بدلاً من كلمات المرور — المفاتيح مُرمّزة بـ base64 ويجب أن تتطابق مع حجم مفتاح التشفير بالضبط
- حماية إعادة التشغيل مُدمجة في البروتوكول (مبنية على الطابع الزمني)
- فصل تشفير الترويسة والحمولة بقيم nonce مختلفة
- تعدد المستخدمين يستخدم PSK على مستوى الخادم + PSK لكل مستخدم (EIH — ترويسة الهوية المشفرة)
الأساليب المدعومة
الأساليب المتاحة تأتي من shadowaead_2022.List:
2022-blake3-aes-128-gcm2022-blake3-aes-256-gcm2022-blake3-chacha20-poly1305
الوارد أحادي المستخدم
الملف: proxy/shadowsocks_2022/inbound.go
service, err := shadowaead_2022.NewServiceWithPassword(config.Method, config.Key, 500, inbound, nil)المصدر: proxy/shadowsocks_2022/inbound.go:55-58
تتولى الخدمة:
- TCP:
service.NewConnection(ctx, connection, metadata) - UDP:
service.NewPacket(ctx, pc, packet, metadata)
الوارد متعدد المستخدمين
الملف: proxy/shadowsocks_2022/inbound_multi.go
يستخدم shadowaead_2022.NewMultiService[int] مع PSK خادم (مُرمّز بـ base64) وكلمات مرور لكل مستخدم:
service, err := shadowaead_2022.NewMultiService[int](config.Method, psk, 500, inbound, nil)
service.UpdateUsersWithPasswords(indices, passwords)المصدر: proxy/shadowsocks_2022/inbound_multi.go:76-86
يتم تحديد هوية المستخدم عبر آلية EIH (ترويسة الهوية المشفرة) الداخلية لمكتبة sing.
الصادر
الملف: proxy/shadowsocks_2022/outbound.go
method, err := shadowaead_2022.NewWithPassword(config.Method, config.Key, nil)
// TCP
serverConn := o.method.DialEarlyConn(connection, singbridge.ToSocksaddr(destination))
// UDP
serverConn := o.method.DialPacketConn(connection)المصدر: proxy/shadowsocks_2022/outbound.go:47-57، proxy/shadowsocks_2022/outbound.go:98-155
UDP عبر TCP (UoT)
يدعم الصادر نقل UDP عبر TCP عند تفعيل UdpOverTcp:
if config.UdpOverTcp {
o.uotClient = &uot.Client{Version: uint8(config.UdpOverTcpVersion)}
}المصدر: proxy/shadowsocks_2022/outbound.go:58-60
المعالج الوارد (خادم SS الكلاسيكي)
الملف: proxy/shadowsocks/server.go
يتعامل الخادم مع TCP وUDP:
func (s *Server) Network() []net.Network {
list := s.config.Network
if len(list) == 0 {
list = append(list, net.Network_TCP)
}
return list
}المصدر: proxy/shadowsocks/server.go:81-87
تدفق TCP: قراءة الترويسة المشفرة، مطابقة المستخدم عبر Validator.Get()، فك تشفير العنوان، التوزيع.
تدفق UDP: كل حزمة UDP تُشفّر بشكل مستقل. يفك الخادم تشفير كل حزمة، يستخرج الوجهة، ويوزّع عبر udp.Dispatcher.
المعالج الصادر (عميل SS الكلاسيكي)
الملف: proxy/shadowsocks/client.go
لـ TCP:
- توليد IV عشوائي، كتابته إلى الاتصال
- إنشاء كاتب تشفير عبر
account.Cipher.NewEncryptionWriter() - كتابة ترويسة عنوان بأسلوب SOCKS5
- بث الحمولة عبر كاتب التشفير
لـ UDP:
- كل حزمة صادرة تُشفّر بشكل مستقل بـ
EncodeUDPPacket() - حزم الاستجابة تُفك تشفيرها بـ
DecodeUDPPacket()
المصدر: proxy/shadowsocks/client.go:48-195
ملاحظات التنفيذ
إهمال تشفيرات التدفق: يوجد نوع
NoneCipherللتوافق مع الإصدارات السابقة لكنه لا يوفر تشفيراً. تمت إزالة تشفيرات التدفق (RC4, ChaCha20 بدون Poly1305) بالكامل.تفرّد IV: رغم وجود نوع خطأ
ErrIVNotUnique، فإن فحص IV معطّل في المُدقق (validator.go:148). يعتمد بروتوكول Shadowsocks الكلاسيكي في Xray على وسم AEAD للمصادقة بدلاً من تتبع IV.تقنيع نوع العنوان: يُقنّع محلل عناوين Shadowsocks البتات العليا 4 من بايت نوع العنوان (
b & 0x0F)، مما يسمح بتضمين أعلام إضافية في البتات العليا.Cone NAT: يدعم كلا الخادمين الكلاسيكي و2022 وضع "cone" لـ UDP. عند التفعيل، تُعيد الحزم اللاحقة من نفس العميل استخدام الوجهة الأولى للتوزيع، محاكيةً سلوك NAT.
وضع الترحيل: يحتوي Shadowsocks 2022 على وارد ترحيل (
inbound_relay.go) يسمح بسلاسل ترحيل متعددة القفزات، رغم أن هذا استخدام أكثر تخصصاً.sing-bridge: يستخدم كود Shadowsocks 2022 مساعدات
singbridgeللتحويل بين أنواع مكتبةsing(مثلM.Socksaddr) وأنواع Xray-core الداخلية (مثلnet.Destination).