التشفير من طرف إلى طرف (E2EE)

يشفّر MorisBox محتوى in-app بحيث لا تخزن الخوادم إلا ciphertext والبيانات الوصفية. ولا تغادر المفاتيح الخاصة أجهزة المستخدم أو الوكيل أبدًا.

ما الذي يُشفّر؟

المحتوىE2EE
محادثات الدعم (المستخدم ↔ وكلاء الشريك)نعم
رسائل المركز التي تحمل encryption: "e2e"نعم
الوسائط داخل أغلفة E2Eنعم
جسم OTP / SMS / الانتقال الاحتياطي عبر WhatsAppلا (يجب أن يكون مقروءًا على هذه القنوات)
البيانات الوصفية (الشريك والطوابع الزمنية والحالة والحجم)لا

تخزن رسائل E2EE المحتوى في in-app فقط. إذا لم يكن المستخدم متصلًا في التطبيق (لم يُسجّل أي نشاط للجهاز منذ نحو 5 دقائق)، يرسل MorisBox أيضًا تنبيهًا مبهمًا عبر WhatsApp (ثم SMS):

لديك رسالة جديدة من {partner} في صندوق MorisBox. افتح التطبيق لقراءتها (رسالة آمنة).

لا يتضمن هذا التنبيه الجسم المشفر أبدًا. لا تستخدم رموز OTP وتسجيل الدخول E2EE، حتى يتمكن المستخدمون دائمًا من الوصول إلى حساباتهم.

البروتوكول (v1)

العنصرالخوارزمية
هوية الجهازX25519
اتفاق المفاتيحECDH (X25519)
KDFHKDF-SHA-256
تشفير المحتوىAES-256-GCM
ترميز النقلBase64url
content_key  →  AES-GCM encrypt(plaintext JSON)
wrap(content_key) per recipient device via ECDH + AES-GCM

لا يتضمن الإصدار v1 آلية double ratchet على نمط Signal (ومن المخطط تعزيزها لاحقًا).

دليل مفاتيح الأجهزة

أجهزة المستخدم

بعد تسجيل الدخول، ينشئ العميل زوج مفاتيح هوية وينشر النصف العام:

POST /api/v1/me/devices/keys
Authorization: Bearer <user_jwt>
Content-Type: application/json

{
  "identity_key_pub": "<b64url x25519 public key>"
}

أجهزة وكلاء الشريك (Partner Hub)

POST /api/v1/agent/devices/keys
Authorization: Bearer <partner_api_key>

{
  "device_uuid": "<browser session uuid>",
  "identity_key_pub": "<b64url>"
}

الحصول على مفاتيح هاتف

GET /api/v1/keys/+228XXXXXXXX
Authorization: Bearer <partner_api_key>
{
  "phone": "+228XXXXXXXX",
  "devices": [
    {
      "device_uuid": "...",
      "identity_key_pub": "...",
      "platform": "web",
      "has_keys": true
    }
  ],
  "agent_devices": [ ... ]
}

إرسال رسالة مشفرة إلى المركز

POST /api/v1/messages
Authorization: Bearer <partner_api_key>
Content-Type: application/json

{
  "phone": "+228XXXXXXXX",
  "encryption": "e2e",
  "e2e_envelope": {
    "v": 1,
    "alg": "x25519-hkdf-sha256-aes-256-gcm",
    "mode": "sealed",
    "key_version": 0,
    "sender_device_uuid": "partner-agent-…",
    "ciphertext": "<b64url>",
    "nonce": "<b64url>",
    "aad": "seed360|msg|<ref>|v1",
    "key_wraps": [
      {
        "device_uuid": "user-device-…",
        "wrap": "<b64url>",
        "wrap_nonce": "<b64url>",
        "sender_eph_pub": "<b64url>"
      }
    ]
  },
  "content_type": "text"
}

يخزن الخادم الغلاف فقط. ولا يُحفظ body بنص عادي مطلقًا.

الأخطاء:

الرمزالمعنى
e2e_no_devicesلم ينشر المستخدم مفاتيح هوية بعد
validatione2e_envelope.ciphertext / nonce مفقود

رسائل المحادثة (العميل)

POST /api/v1/me/conversations/<ref>/messages
Authorization: Bearer <user_jwt>

{
  "encrypted": true,
  "e2e_mode": "conversation",
  "e2e_key_version": 3,
  "e2e_envelope": { ... }
}

حمّل أغلفة مفتاح المحتوى:

POST /api/v1/me/conversations/<ref>/keys
{
  "key_version": 3,
  "wraps": [
    {
      "device_uuid": "...",
      "device_kind": "user",
      "wrap": "...",
      "wrap_nonce": "...",
      "sender_eph_pub": "..."
    }
  ]
}

دليل المشاركين:

GET /api/v1/me/conversations/<ref>/devices

سلوك العميل

العميلالسلوك
Application Client / الهاتفإنشاء المفاتيح عند تسجيل الدخول؛ تشفير الدردشة؛ فك تشفير الأغلفة للعرض
Partner Hubنشر مفاتيح الوكيل؛ تشفير الردود / الإرسال المختوم إلى المركز
الخادمتوجيه ciphertext؛ من دون فك تشفيره مطلقًا

تعرض الواجهة رمز قفل على الرسائل المشفرة. إذا لم يكن لهذا الجهاز wrap، يرى المستخدم «تعذر فك التشفير».

ملاحظات الأمان

  • تبقى المفاتيح الخاصة على الجهاز (SecureStore / التخزين المحلي؛ وانتقل إلى مفاتيح مدعومة بالأجهزة عند توفرها).
  • يربط AAD الغلاف بسلسلة سياق الرسالة لتقليل هجمات الاستبدال.
  • تُستبعد الأجهزة الملغاة من أغلفة wraps الجديدة.
  • لا تسجّل الأجسام التي فُك تشفيرها على الخوادم أو البوابات.

راجع أيضًا