مرجع تنفيذ التشفير
التشفير الذي يعمل فعليًا أضيق من التشفير المكتوب، وهذه الفجوة هي موضوع هذه الصفحة. صناديق sealed box الخاصة بـ libsodium هي التشفير الوحيد للرسائل الذي ينفّذه Railgun — وهي لا تُنفَّذ إلا على مسار العميل الذي تكون فيه وحدة libsignal الأصلية غائبة. وفي نسخة سطح المكتب Electron، حيث تُحمَّل libsignal فعلًا، لا يزال المعالج الذي يشفّر الرسائل المباشرة مجرد عنصر نائب يرمّز النص الصريح بـ base64. أما رسائل المجتمعات والقنوات فهي غير مشفَّرة على أي مسار. والبروتوكول على طراز Signal الموصوف في النصف الثاني من هذه الصفحة مكتوب ومُراجَع، وغير موصول بأي عميل يمكنك تنزيله.
هذا مرجع معماري، وليس تدقيقًا أمنيًا مستقلًا. يصف القسمان 1 و2 ما يتم شحنه فعليًا. أما الأقسام من 4 إلى 7 فتصف تصميمًا يعمل Railgun على بنائه، وهي موسومة بـ مخطَّط له في كل موضع. ولا ينبغي قراءة أي شيء هنا على أنه ادعاء بأن أي عميل من عملاء Railgun يفرض حاليًا خاصية موسومة بأنها مخطَّط لها.
ما يتم شحنه اليوم
| المسار | الآلية | الحالة |
|---|---|---|
| الرسائل المباشرة — نسخ المتصفح والتطوير | sealed box من libsodium — اتفاق مفاتيح X25519، وتشفير موثَّق XSalsa20-Poly1305. يُختار عندما تكون وحدة libsignal الأصلية غائبة. | مشفَّر |
| الرسائل المباشرة — نسخة سطح المكتب Electron | تُحمَّل libsignal، لذا يُوجَّه encryptDm إلى معالج IPC في Electron — وهو عنصر نائب يرمّز النص الصريح بـ base64. ويستقبل المُرحِّل نصًا قابلًا للقراءة. | غير مشفَّر |
| رسائل المجتمعات والقنوات | تُرسَل كنص صريح مغلَّف بـ base64. تشفير القنوات مكتوب لكنه يعيد علامة نص صريح. | غير مشفَّر |
| X3DH + Double Ratchet | منفَّذ اعتمادًا على libsignal، لكنه مستورَد كنوع فقط. لا يوجد أي مسار تشغيل ينشئ نسخة منه. | مخطَّط له |
| تخزين المفتاح الخاص | تُولَّد المفاتيح وتُحفَظ على الجهاز. ولا يوجد أي مسار في الشيفرة ينقل المفاتيح الخاصة أو مادة مفتاح الهوية. | منفَّذ |
1. صناديق sealed box — التشفير الذي يعمل
حيثما يشفّر Railgun رسالة مباشرة أصلًا، فإنه يفعل ذلك عبر sealed box من libsodium — بنية crypto_box_seal. وهي تجمع بين اتفاق مفاتيح X25519 والتشفير الموثَّق XSalsa20-Poly1305. هذه تعمية حقيقية ومُراجَعة جيدًا. وهي أيضًا بنية بسيطة عن قصد، وحدودها مهمة. أما أي العملاء يصل إليها فهو موضوع القسم 2: نسخ المتصفح والتطوير تصل إليها؛ ونسخة سطح المكتب Electron لا تصل.
كيف يُبنى الـ sealed box
// Sender has only the recipient's public key, PK_r
(pk_e, sk_e) = crypto_box_keypair() // fresh, single-use
nonce = BLAKE2b(pk_e || PK_r) // derived, not random
ct = crypto_box(plaintext, nonce, PK_r, sk_e)
sealed = pk_e || ct // ephemeral key is prepended
erase(sk_e)
يستخرج المستلِم المفتاح العام العابر من مقدمة الرسالة، ويعيد حساب السر المشترك نفسه بمفتاحه الخاص، ثم يفتح الصندوق. ووجود وسم Poly1305 يعني أن أي نص مشفَّر معدَّل يفشل في الفتح بدلًا من أن يُفَك إلى بيانات مشوَّهة.
ما الذي يمنحك إياه
- • السرية في مواجهة المُرحِّل وأي طرف على الشبكة.
- • السلامة — يُكتشَف العبث ولا يُفَك التشفير بصمت.
- • إخفاء هوية المرسِل: المفتاح العابر لا يكشف شيئًا عمّن أرسل الرسالة.
- • لا حاجة إلى إنشاء جلسة، لذا يعمل حتى عندما يكون المستلِم غير متصل.
ما الذي لا يمنحك إياه
- • لا توجد سرية تامة للأمام. المفتاح الخاص طويل الأمد للمستلِم يفتح كل رسالة أُرسلت إليه على الإطلاق. وإذا تسرّب، تسرّب معه أي نص مشفَّر تم التقاطه.
- • لا توجد مصادقة للمرسِل. الـ sealed box مجهول المصدر بحكم بنيته؛ وهو لا يثبت شيئًا عمّن كتبه.
- • لا يوجد تعافٍ بعد الاختراق. لا يوجد ratchet يتجاوز اختراق المفاتيح.
هذه الفجوات الثلاث هي بالضبط ما وُجد X3DH وDouble Ratchet لسدّه، ولهذا يجري العمل عليهما. وإلى أن يكتمل ذلك، فإن الملخص الصادق لرسالة مباشرة عبر sealed box هو: قوية أمام مراقب سلبي للشبكة وأمام المُرحِّل، وضعيفة أمام خصم يحصل في النهاية على مفتاح جهاز المستلِم. وهذا الملخص ينطبق فقط حيث تعمل هذه الشيفرة فعليًا.
2. ما هو غير مشفَّر
رسائل المجتمعات والقنوات — وهي الجزء الأكبر مما يكتبه معظم مستخدمي Railgun فعليًا — غير مشفَّرة على أي مسار عميل اليوم. دالة تشفير القنوات موجودة، وهي صريحة بشأن نفسها: تسجّل تحذيرًا وتعيد النص الصريح مغلَّفًا بـ base64 خلف سلسلة علامة، حتى لا يخلط أحد في المراحل التالية بينه وبين نص مشفَّر.
// What encryptChannel actually returns
logger.warn("Channel encryption not yet implemented")
ciphertext = base64("[DEVCRYPTO:PLAINTEXT]" + plaintext)
النتيجة العملية: بالنسبة إلى محتوى المجتمعات والقنوات، فإن خادم Railgun في الموضع نفسه الذي يقف فيه أي خادم دردشة عادي. يمكنه قراءة ما تكتبه هناك. أما توزيع مفاتيح المجموعات — مفتاح لكل قناة يُسلَّم إلى الأعضاء داخل صناديق sealed box — فهو الحل المخطَّط له وهو غير مشحون.
الرسائل المباشرة في نسخة سطح المكتب Electron
الفجوة الثانية هي الرسائل المباشرة على العميل الذي يشغّله معظم الناس، ومن السهل صياغتها بالمقلوب، ولذا إليك منطق الاختيار كما تنفّذه الشيفرة. تسأل initCrypto() العمليةَ الرئيسية عمّا إذا كانت libsignal قد حُمِّلت. و@signalapp/libsignal-client اعتمادية حقيقية ومطلوبة عند بدء التشغيل، لذا فهي تُحمَّل فعلًا في نسخة Electron المشحونة — وهذه الإجابة هي ما يختار تنفيذ IPC الخاص بـ Electron بدلًا من تنفيذ sealed box. وencryptDm الخاص به يمرّر الطلب إلى معالج crypto:encryptDm، وهو عنصر نائب:
// crypto:encryptDm, Electron main process
const plaintextBytes = new TextEncoder().encode(plaintext)
ciphertext: toBase64(plaintextBytes) // not encryption
إذن يتم الوصول إلى صناديق sealed box المذكورة في القسم 1 عندما تكون libsignal غائبة — أي في المتصفح وفي بيئة التطوير. وعلى عميل سطح المكتب، تصل الرسالة المباشرة إلى المُرحِّل كنص صريح مرمَّز بـ base64. وتُحمَّل libsignal هناك لتوليد مفتاح هوية وحزمة مفاتيح مسبقة؛ لكنها لا تنشئ أي جلسة ولا تدير أي ratchet — فـ crypto:ensureDmSession ليس سوى سجل تصحيح بلا محتوى.
3. Curve25519 — المنحنى الأساسي
كلٌّ من صناديق sealed box التي يتم شحنها وتصميم X3DH الذي لا يتم شحنه يقوم على المنحنى الإهليلجي نفسه: Curve25519، الذي صمّمه Daniel J. Bernstein لتبادل مفاتيح ديفي-هيلمان عالي السرعة. يصف هذا القسم المنحنى نفسه، وينطبق على الاثنين معًا.
معادلة المنحنى
y² = x³ + 486662x² + x
over the prime field 𝔽ₚ where p = 2²⁵⁵ − 19
اختير العدد الأولي 2²⁵⁵ − 19 لأنه يتيح حسابًا تمثيليًا سريعًا للغاية. واختير المعامل 486662 ليكون أصغر قيمة تنتج منحنى آمنًا بالخصائص الأمنية المطلوبة.
توليد المفاتيح
1. المفتاح الخاص: وَلِّد 32 بايتًا عشوائيًا باستخدام CSPRNG (مولّد أعداد شبه عشوائية آمن تعمويًا). ثبِّت المفتاح بتصفير أدنى 3 بتات وأعلى بت، وضبط البت الأعلى الثاني. وهذا يضمن أن المفتاح مضاعف للعدد 8 وضمن النطاق الصالح.
a = random(32 bytes)
a[0] &= 248 // Clear low 3 bits
a[31] &= 127 // Clear high bit
a[31] |= 64 // Set second-highest bit
2. المفتاح العام: احسب الضرب السلمي للمفتاح الخاص بنقطة الأساس G = 9.
A = a · G // Scalar multiplication on Curve25519
يعتمد الأمان على مسألة اللوغاريتم المتقطع على المنحنيات الإهليلجية (ECDLP): بمعلومية A وG، يستحيل حسابيًا استعادة a. ويوفّر Curve25519 نحو 128 بت من الأمان.
تبادل مفاتيح ديفي-هيلمان
يمكن لطرفين (أليس وبوب) حساب سر مشترك دون إرساله أبدًا:
Alice: a (private), A = a·G (public)
Bob: b (private), B = b·G (public)
Alice computes: S = a · B = a · (b·G)
Bob computes: S = b · A = b · (a·G)
Both arrive at S = ab·G — the same shared secret
المتنصّت الذي يعرف A وB (وكلاهما عام) لا يستطيع حساب S دون حل ECDLP. وهذا هو افتراض ديفي-هيلمان الحسابي (CDH).
الـ sealed box هو هذا التبادل نفسه مع جعل أحد الطرفين عابرًا ثم التخلص منه. أما X3DH، أدناه، فهو هذا التبادل مُنفَّذًا أربع مرات على أزواج مفاتيح مختلفة، بحيث تصادق النتيجة على الطرفين إضافةً إلى إخفاء المحتوى.
4. X3DH — تبادل ديفي-هيلمان الثلاثي الموسَّع
مخطَّط لهيحل X3DH مشكلة صعبة: كيف ينشئ شخصان جلسة مشفَّرة بينما يكون أحدهما غير متصل؟ يتطلب ديفي-هيلمان التقليدي أن يكون الطرفان متصلين. ويستخدم X3DH حزم مفاتيح مرفوعة مسبقًا لتمكين اتفاق المفاتيح غير المتزامن.
أنواع المفاتيح
مفتاح الهوية (IK)
زوج مفاتيح Curve25519 طويل الأمد. يُولَّد مرة واحدة، ويعرّف المستخدم تعمويًا. ولا يتغير أبدًا ما لم يُعِد المستخدم التسجيل.
المفتاح المسبق الموقَّع (SPK)
زوج مفاتيح Curve25519 متوسط الأمد، موقَّع بمفتاح الهوية. يُدوَّر دوريًا. والتوقيع يثبت أنه يعود لحامل مفتاح الهوية.
المفتاح المسبق أحادي الاستخدام (OPK)
أزواج مفاتيح Curve25519 عابرة تُرفَع على دفعات. يُستخدم كل منها مرة واحدة ثم يُحذَف. ويوفّر طبقة إضافية من السرية التامة للأمام للرسالة الأولى.
المفتاح العابر (EK)
زوج مفاتيح Curve25519 جديد يولّده المرسِل لكل جلسة جديدة. ولا يُخزَّن أبدًا على الخادم.
مصافحة X3DH
عندما تريد أليس مراسلة بوب (الذي قد يكون غير متصل)، تجلب حزمة مفاتيحه المسبقة من الخادم وتنفّذ أربع عمليات ديفي-هيلمان:
// Alice has: IKₐ (her identity key), EKₐ (fresh ephemeral)
// Bob published: IKᵦ, SPKᵦ, OPKᵦ
DH1 = DH(IKₐ, SPKᵦ) // her identity ↔ his signed pre-key
DH2 = DH(EKₐ, IKᵦ) // her ephemeral ↔ his identity
DH3 = DH(EKₐ, SPKᵦ) // her ephemeral ↔ his signed pre-key
DH4 = DH(EKₐ, OPKᵦ) // her ephemeral ↔ his one-time pre-key
SK = KDF(DH1 || DH2 || DH3 || DH4)
// SK is the shared secret, derived via HKDF-SHA-256
لماذا أربع عمليات DH؟
- DH1 يوفّر مصادقة متبادلة (مفتاحا الهوية كلاهما مشمولان)
- DH2 يضمن ارتباط المفتاح العابر بهوية بوب
- DH3 يوفّر سرية تامة للأمام عبر المفتاح العابر
- DH4 يوفّر سرية تامة للأمام إضافية. وإذا لم يتوفر OPK، فإن X3DH يظل يعمل بـ DH1–DH3 وحدها
DH1 هو العملية التي لا يملكها الـ sealed box. ولهذا لا يستطيع المسار المشحون أن يخبرك بمصدر الرسالة.
KDF — دالة اشتقاق المفاتيح
تُدمَج مخرجات DH الخام باستخدام HKDF-SHA-256 (دالة اشتقاق مفاتيح قائمة على HMAC). تستخلص HKDF العشوائية من مخرجات DH المتسلسلة وتوسّعها إلى سر مشترك عشوائي بانتظام:
PRK = HKDF-Extract(salt="", input=DH1||DH2||DH3||DH4)
SK = HKDF-Expand(PRK, info="RailGunX3DH", length=32)
5. خوارزمية Double Ratchet
مخطَّط لهبمجرد أن ينشئ X3DH سرًا مشتركًا أوليًا، يتولى Double Ratchet المهمة. وهو يستخدم آليتَي "ratchet" متشابكتين لاشتقاق مفتاح فريد جديد لكل رسالة. يحمي التصميم الرسائل السابقة، والهدف منه استعادة الحماية للرسائل المستقبلية بعد خطوة ratchet جديدة.
DH Ratchet (غير متماثل)
في كل مرة يتغير فيها اتجاه المحادثة (أليس → بوب، ثم بوب → أليس)، يحدث تبادل مفاتيح ديفي-هيلمان جديد باستخدام مفاتيح عابرة جديدة. وهذا "يدير" المفتاح الجذري إلى الأمام:
dh_out = DH(my_ratchet_key, their_ratchet_key)
root_key, chain_key = KDF(root_key, dh_out)
هذا ما كان سيوفّر الأمان بعد الاختراق: المهاجم الذي يسرق الحالة الراهنة يفقد الوصول بمجرد حدوث خطوة DH ratchet جديدة.
Ratchet متماثل (سلسلة تجزئة)
بين خطوات DH ratchet، يشتق ratchet قائم على التجزئة مفتاح رسالة جديدًا من مفتاح السلسلة لكل رسالة:
message_key = HMAC-SHA256(chain_key, 0x01)
chain_key = HMAC-SHA256(chain_key, 0x02)
يُحذَف مفتاح السلسلة القديم بعد كل خطوة. ويشفّر مفتاح الرسالة رسالة واحدة بالضبط ثم يُحذَف. وهذه هي آلية السرية التامة للأمام.
تدرّج الـ Ratchet
بصريًا، يبدو Double Ratchet هكذا. كل سهم هو دالة أحادية الاتجاه — يمكنك المضي قدمًا لكن ليس إلى الوراء أبدًا:
Root Key Chain:
RK₀ ──DH──→ RK₁ ──DH──→ RK₂ ──DH──→ RK₃ ...
Each RK step produces a Chain Key:
↓ ↓ ↓
CK₀ CK₁ CK₂
↓ ↓ ↓
Each CK step produces Message Keys:
CK₀→MK₀ CK₁→MK₃ CK₂→MK₅
CK₀→MK₁ CK₁→MK₄ CK₂→MK₆
CK₀→MK₂ CK₂→MK₇
كان كل MKₙ ليشفّر رسالة واحدة بالضبط ثم يُحذَف، بحيث لا يستطيع مهاجم يحصل على MK₄ أن يشتق MK₃ أو MK₅. أما على مسار sealed box الذي يتم شحنه اليوم فلا يوجد مفتاح لكل رسالة يمكن سرقته ولا سلسلة يمكن تتبعها — بل مفتاح واحد طويل الأمد يفتح كل شيء.
6. السرية التامة للأمام — ما الذي يتطلبه الأمر
مخطَّط لهالسرية التامة للأمام تعني أن اختراق المفاتيح طويلة الأمد لا يخترق مفاتيح الجلسات السابقة. Railgun لا يملكها اليوم. وهناك آليتان كانتا ستوفرانها، وكلتاهما تنتمي إلى البروتوكول المخطَّط له أعلاه:
المفاتيح العابرة في X3DH
يولّد المرسِل زوج مفاتيح عابرًا جديدًا لكل جلسة جديدة ويحذف نصفه الخاص بعد المصافحة. والمهاجم الذي يسرق لاحقًا مفتاحَي هوية الطرفين يظل عاجزًا عن إعادة بناء سر الجلسة. وصناديق sealed box تستخدم بالفعل مفتاحًا عابرًا جديدًا من جهة المرسِل — لكن نصف التبادل الخاص بالمستلِم هو مفتاحه طويل الأمد، وهذا بالضبط سبب عدم تحقق الخاصية.
حذف مفاتيح الـ ratchet
يحذف Double Ratchet مفاتيح السلسلة ومفاتيح الرسائل القديمة بعد استخدامها. وسلسلة KDF أحادية الاتجاه — فبمعلومية CKₙ يمكنك حساب CKₙ₊₁ لكن ليس CKₙ₋₁، وبذلك لا يكشف اختراق عند اللحظة T أي شيء أُرسل قبل T.
الأمان بعد الاختراق
يوفّر DH ratchet أيضًا سرية مستقبلية: المهاجم الذي يخترق حالة الجلسة الراهنة يفقدها عند خطوة الـ ratchet التالية، التي تُدخل عشوائية لا يملكها. وبدون ratchet، يبقى المفتاح المخترق نافعًا للمهاجم إلى أجل غير مسمى.
7. بنية المُرحِّل وقائمة الانتظار
يقوم Railgun بترحيل المغلَّفات وقد يضعها مؤقتًا في قائمة انتظار للتسليم عند عدم الاتصال. وقد بُني المُرحِّل بحيث لا يحتاج إلى النص الصريح للرسائل، لكنه اليوم يستقبل نصًا صريحًا في حالتين: رسائل القنوات، التي لا يشفّرها أي عميل، والرسائل المباشرة من نسخة سطح المكتب Electron، التي يقوم معالج IPC الخاص بها بترميزها بـ base64 بدلًا من تشفيرها. أما الرسالة المباشرة القادمة من مسار sealed box فتصل كنص مشفَّر لا يستطيع المُرحِّل فتحه.
ما يراه الخادم
ما يحتفظ به الخادم:
- • حزم المفاتيح العامة
- • معلومات تسجيل المستخدم
- • البيانات الوصفية للمجتمعات/القنوات والعضوية
- • نص مشفَّر بـ sealed box لا يستطيع فتحه (الرسائل المباشرة من مسار المتصفح/التطوير)
- • نص صريح لرسائل مباشرة مرمَّز بـ base64 من نسخة سطح المكتب Electron
- • محتوى رسائل القنوات، إلى أن يُشحَن تشفير القنوات
- • سجلات تدقيق المصادقة
ما لا يستقبله الخادم أبدًا:
- • المفاتيح الخاصة أو مفاتيح الهوية
- • حالة جلسة العميل
- • نسخة دائمة من سجل رسائلك
تُرحَّل الرسائل إلى المستلمين المتصلين في الوقت الفعلي عبر WebSocket. أما الأجهزة غير المتصلة فتوضع مغلَّفاتها في قائمة انتظار في Redis بمدة صلاحية قصوى 30 يومًا وتُحذَف بمجرد أن يؤكد الجهاز استلامها. ولا يوجد أرشيف رسائل على الخادم — فسجل العميل يعيش في التخزين المحلي على جهازك أنت، ولذا فإن فقدان الجهاز يعني فقدان السجل.
8. ملخص الخصائص الأمنية
| الخاصية | الآلية | الحالة |
|---|---|---|
| سرية الرسائل المباشرة | صناديق sealed box على مسار المتصفح/التطوير فقط؛ أما معالج سطح المكتب Electron فيرمّز بـ base64 | جزئي |
| سلامة الرسائل المباشرة | وسم Poly1305 على مسار sealed box؛ أما مسار سطح المكتب Electron فيرسل base64 غير موثَّق | جزئي |
| بقاء المفاتيح على الجهاز | مخزن مفاتيح محلي؛ ولا يوجد أي مسار في الشيفرة ينقل المفاتيح الخاصة | منفَّذ |
| التسليم عند عدم الاتصال | مغلَّفات موضوعة في قائمة انتظار في Redis بمدة صلاحية 30 يومًا، وتُحذَف عند الإقرار بالاستلام | منفَّذ |
| سرية القنوات | توزيع مفاتيح المجموعات غير منفَّذ؛ ويُرسَل المحتوى قابلًا للقراءة | غير منفَّذ |
| مصادقة المرسِل | صناديق sealed box مجهولة المصدر بحكم بنيتها | غير منفَّذ |
| السرية التامة للأمام | تتطلب Double Ratchet، وهو ما لا ينشئه أي عميل | مخطَّط له |
| التعافي بعد الاختراق | يتطلب DH ratchet، وهو ما لا ينشئه أي عميل | مخطَّط له |
| تدقيق مستقل | لا يُدَّعى وجود أي تدقيق تعموي مكتمل من طرف ثالث | غير مكتمل |
| ضمان مقاومة الحوسبة الكمّية | لا يُدَّعى أي ضمان شامل للأمان في مواجهة الحوسبة الكمّية | غير مُدَّعى |
من أين تأتي هذه الصفحة
كل حالة مذكورة أعلاه قُرئت من شيفرة العميل المصدرية لا من وثيقة تصميم. ومستودع العميل ليس عامًا حاليًا، لذا لا يمكننا أن نطلب منك التحقق بنفسك — وهذا سبب لاعتبار هذه الصفحة إفصاحًا لا دليلًا. والأسئلة حول أي ادعاء بعينه مرحَّب بها على security@railgun.chat.