Files
tripz-llc/docs/16-encryption.md
T
Hamza-AyedandClaude Opus 4.8 253fbb4b2a feat: المجموعة E (كشف الاحتيال) + تشفير الهاتف at-rest
E — اتصال ثم إلغاء (من driver_ride_scam عند سيرو):
- E1: driver_called_passenger + last_call_at/by/count على الرحلة نفسها لا
  Redis فقط — حقيقة دائمة تفيد التحقيق بعد انتهاء الرحلة وزوالها من الكاش.
  يُسجَّل عند call:offer (بدء المكالمة الفعلي) في RealtimeGateway عبر
  Repository<Trip> مباشرة — لا استيراد TripsModule كاملاً (يستورد
  RealtimeModule أصلاً، فدورة استيراد)
- E2: FraudService.recordCallThenCancel — عدّاد يومي منفصل عن عدّاد الإلغاء
  العام بالساعة. 3/يوم = إنذار لا يمنع الإلغاء، 6/يوم = حظر مؤقت (طلب
  المالك حرفياً). يرتبط فقط إن كانت المكالمة خلال 30 دقيقة من الإلغاء

تشفير الهاتف at-rest (ملاحظة المالك) — بفهرس أعمى منفصل، لا تشفير ساذج:
- AES-256-GCM عشوائي IV يمنع WHERE phone = :x من المطابقة أصلاً (كل تشفير
  مختلف شكلاً حتى لنفس الرقم) — لو طُبِّق التشفير مباشرة بلا هذا لكسر تسجيل
  الدخول بالكامل بصمت
- users.phone_bidx = HMAC-SHA256(الرقم المطبَّع) — حتمي، يصلح للبحث
  وللقيد الفريد. القيد انتقل من (tenant_id, phone) إلى (tenant_id,
  phone_bidx) — القديم كان سيقبل تكرار نفس الرقم الحقيقي فعلياً
- findByPhone/create يحسبان البصمة؛ لا نقطة أخرى تقرأ phone بمساواة خام
- هجرة EncryptPhone تُشفّر الصفوف القائمة فوراً وتبني البصمة بأثر رجعي —
  استثناء متعمَّد من تسامح decrypt() المعتاد (الهاتف عمود بحث يومي، خطره
  كنص صافٍ أعلى من name)
- يعتمد على تطبيع D1: بصمة حتمية لنفس *النص*، فلا بد أن يصل الرقم مطبَّعاً
  قبلها وإلا عاد نفس الرقم بصيغتين ليصير رقمين مرة أخرى

هجرات: TripCallSignal · EncryptPhone.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-17 17:25:18 +03:00

4.0 KiB

16 — التشفير (معيار at-rest)

تصحيح لخطأ سيرو الموثّق (CBC بـ IV ثابت). المعيار عندنا: AES-256-GCM + IV عشوائي لكل قيمة.

المعيار

  • الخوارزمية: AES-256-GCM (تشفير موثَّق authenticated — يكشف أي عبث).
  • IV: عشوائي 96-بت لكل عملية (randomBytes(12)) — لا IV ثابت إطلاقاً.
  • صيغة التخزين: v1:base64(iv | authTag | ciphertext) — الإصدار في المقدمة للترقية المستقبلية.
  • المفتاح: ENCRYPTION_KEY (32 بايت hex؛ openssl rand -hex 32). إلزامي للإنتاج؛ بدونه مفتاح تطوير غير آمن + تحذير عند الإقلاع.
  • الكود: common/crypto/crypto.util.ts — encrypt / decrypt / blindIndex / EncryptedTransformer.

كيف يُطبَّق (بلا تعقيد)

عبر TypeORM transformer على العمود: يشفّر عند الكتابة ويفكّ عند القراءة تلقائياً. لا يتغير منطق الخدمات ولا استجابات الـ API (تظهر مفكوكة).

@Column({ type: 'varchar', nullable: true, transformer: EncryptedTransformer })
name: string;

المطبّق حالياً

  • users.name · users.phone · drivers.vehicle_plate.

الحقول القابلة للبحث (مثل الهاتف) — ✅ منفَّذ (docs/17 — D1 إضافة)

تشفير GCM عشوائي يمنع المطابقة بالتساوي (كل تشفير مختلف) — WHERE phone = :x لا يطابق أبداً حتى مع نفس الرقم. الحل: فهرس أعمى (blind index) — users.phone_bidx = HMAC-SHA256(canonicalPhone) حتمي، عمود إضافي غير مشفَّر (بصمة أحادية الاتجاه، لا تكشف الرقم). كل بحث وتفرّد على الهاتف يمرّ عبره لا عبر phone:

  • UsersService.findByPhone يحسب blindIndex(phone) ويبحث به.
  • القيد الفريد (tenant_id, phone_bidx) — لا (tenant_id, phone) (ذاك كان سيقبل تكرار نفس الرقم الحقيقي لأن كل تشفير مختلف شكلاً).
  • يعتمد على تطبيع D1: البصمة حتمية لنفس النص، فلا بد أن يصل phone مطبَّعاً (common/phone/phone.service.ts) قبل الوصول لهذا الكود — وإلا اعتُبر نفس الرقم بصيغتين مختلفتين رقمين مختلفين مرة أخرى.

الميغريشن (صحيح ومتوافق)

  • لا تغيير مخطّط لـvarchar: الأعمدة تتّسع للنص المشفّر (base64). لا هجرة لتحويل النوع.
  • تسامح انتقالي: decrypt يُرجع النص القديم غير المشفّر كما هو (يبدأ بلا v1:) — فلا تنكسر القراءة على بيانات قديمة.
  • الهاتف استثناء متعمَّد لهذا التسامح: هجرة EncryptPhone تُشفّر الصفوف القائمة فوراً (لا تتركها صافية للأبد بالاعتماد على decrypt المتسامح) — لأن phone عمود بحث نشط يومياً (كل تسجيل دخول)، ونصّه الصافي في القاعدة أخطر من name (نادراً ما يُقرأ مباشرة كنص خام). نفس الهجرة تبني phone_bidx بأثر رجعي لكل صف قائم.
  • عند نقل بيانات حقيقية من سيرو: تُقرأ مفكوكةً من سيرو ثم تُكتب فتُشفَّر عندنا تلقائياً — ويجب تمريرها عبر PhoneService.normalize أولاً قبل الكتابة، وإلا فسدت البصمة.

ما يُشفَّر مستقبلاً

وثائق السائق وأرقام الهوية/الرخصة (وحدة الوثائق)، ووجهات السحب الحساسة، وأي PII يُضاف — بنفس المحوّل.

← يُقرأ مع: 14-server-conventions · 08-data-model