# 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 (تظهر مفكوكة). ```ts @Column({ type: 'varchar', nullable: true, transformer: EncryptedTransformer }) name: string; ``` ## المطبّق حالياً - `users.name` · `drivers.vehicle_plate`. ## الحقول القابلة للبحث (مثل الهاتف) — الحل الصحيح تشفير GCM عشوائي **يمنع المطابقة بالتساوي** (كل تشفير مختلف). للبحث نستخدم **فهرس أعمى (blind index)**: عمود إضافي = `HMAC-SHA256(القيمة)` حتمي للبحث + العمود المشفّر للتخزين. مخطّط عند بناء بحث الهاتف/الهوية. ## الميغريشن (صحيح ومتوافق) - **لا تغيير مخطّط:** الأعمدة `varchar` تتّسع للنص المشفّر (base64). لا هجرة لازمة لتحويل النوع. - **تسامح انتقالي:** `decrypt` يُرجع النص القديم غير المشفّر كما هو (يبدأ بلا `v1:`) — فلا تنكسر القراءة على بيانات قديمة؛ والكتابة الجديدة تُشفّر. - **عند نقل بيانات حقيقية من سيرو:** تُقرأ مفكوكةً من سيرو ثم تُكتب فتُشفَّر عندنا تلقائياً. ## ما يُشفَّر مستقبلاً وثائق السائق وأرقام الهوية/الرخصة (وحدة الوثائق)، ووجهات السحب الحساسة، وأي PII يُضاف — بنفس المحوّل. ← يُقرأ مع: [14-server-conventions](14-server-conventions.md) · [08-data-model](08-data-model.md)