---
title: تدوير المفاتيح
icon: rotate
---

يمتلك Twenty عائلتين مستقلتين من المفاتيح:

* **مفاتيح توقيع JWT** — أزواج مفاتيح غير متماثلة ES256 (مع علامة `kid`) مُخزَّنة في `core."signingKey"`، تُستخدم لتوقيع والتحقق من رموز الوصول / التحديث.
* **مفتاح التشفير أثناء السكون (At-rest encryption key)** — `ENCRYPTION_KEY`، يُستخدم لتشفير رموز OAuth، ومتغيرات التطبيق، ومفاتيح التوقيع الخاصة، وقيم الإعدادات الحساسة، وأسرار TOTP داخل غلاف `enc:v2:`.

يُعد `APP_SECRET` سراً قديماً مُحتفَظاً به لأغراض التوافق مع الإصدارات السابقة: عندما لا يكون `ENCRYPTION_KEY` مضبوطاً، فإنه يعمل كحل احتياطي لمفتاح التشفير أثناء السكون / ملفات تعريف الارتباط للجلسة، ولا يزال يتحقق من رموز الوصول HS256 الموجودة مسبقاً. سيتم إهماله (إيقاف دعمه).

## مفاتيح توقيع JWT

يحمل كل مفتاح قيمة `publicKey` (يُحتفَظ بها إلى أجل غير مسمى حتى يمكنها التحقق من الرموز المصدرة مسبقاً)، و`privateKey` مُشفَّراً (يُستخدم فقط أثناء كون المفتاح حالياً)، وراية `isCurrent` (صف واحد فقط في كل وقت)، وحقل `revokedAt` اختياري.

### تدوير المفتاح الحالي

اضبط `SIGNING_KEY_ROTATION_DAYS` للتفعيل: عندها تصدر مهمة cron يومية مفتاحًا حاليًا جديدًا بمجرد أن يصبح المفتاح القائم أقدم من تلك العتبة. لا يتم إبطال المفاتيح السابقة، لذلك تستمر الرموز الموقعة تحتها في التحقق. اترك المتغير غير معيّن لتعطيل التدوير التلقائي.

<Note>ميزة التدوير التلقائي متوفّرة ابتداءً من الإصدار v2.6+.</Note>

### إبطال مفتاح (في حالات التسريب / الطوارئ فقط)

**Settings → Admin Panel → Signing keys → Revoke** على صف غير حالي. تعمل على مسح المادة الخاصة المشفرة، وتعيين `revokedAt`، ورفض كل رمز حالي موقَّع تحت هذا الـ `kid`.

## تدوير `ENCRYPTION_KEY`

<Note>أمر `secret-encryption:rotate` الموضَّح أدناه متوفر ابتداءً من الإصدار v2.6+.</Note>

يُغلَّف كل مقدار مُشفَّر بالشكل `enc:v2:\<keyId>:\<payload>`، حيث إن `\<keyId>` هو بادئة مكوَّنة من 8 أرقام ست عشرية مشتقة من المفتاح الخام. تتم عملية التدوير (الاستبدال) أثناء العمل، وقابلة للاستئناف.

1. **إنشاء مفتاح جديد**: `openssl rand -base64 32`.

2. **ضَبْط المفتاحين معاً جنباً إلى جنب** في ملف `.env`، ثم إعادة تشغيل الخدمة:
   ```ini
   ENCRYPTION_KEY=NEW_VALUE
   FALLBACK_ENCRYPTION_KEY=OLD_VALUE
   ```
   تستخدم عمليات الكتابة الجديدة المفتاح الجديد، بينما لا تزال الصفوف الحالية تُفك تشفيرها عبر المفتاح الاحتياطي (fallback).

3. **إعادة تشفير الصفوف الحالية**:

   ```bash
   docker exec -it {server_container} yarn command:prod secret-encryption:rotate
   ```

   يتناول الأمر ستة مواقع (`connected-account-tokens`، `application-variable`، `application-registration-variable`، `signing-key-private-keys`، `sensitive-config-storage`، `totp-secrets`). يُهمِل عامل تصفية SQL الصفوف الموجودة بالفعل على `\<keyId>` الجديد، لذلك يكون الأمر عديم الأثر التكراري (idempotent): يمكنك مقاطعته وإعادة تشغيله حسب الحاجة. يخرج بقيمة مختلفة عن الصفر إذا فشل أي صف — أعد تشغيله لإعادة المحاولة.

   | خيار                                     | الوصف                                                         |
   | ---------------------------------------- | ------------------------------------------------------------- |
   | `-s, --site \<site>`    | حصر التنفيذ على موقع واحد فقط.                                |
   | `-b, --batch-size \<n>` | عدد الصفوف في كل دفعة (الافتراضي `200`، والحد الأقصى `5000`). |
   | `-d, --dry-run`                          | فك التشفير + إعادة التشفير في الذاكرة، مع تخطي جملة `UPDATE`. |

4. **إزالة المفتاح الاحتياطي (fallback)** بمجرد أن يُظهِر الخيار `--dry-run` عدم وجود صفوف متبقية: أزِل `FALLBACK_ENCRYPTION_KEY` وأعد التشغيل.

## دعم `APP_SECRET` القديم

تستخدم النُسخ الأقدم التي لم تُعيِّن `ENCRYPTION_KEY` مطلقاً المتغيِّر `APP_SECRET` كمفتاح التشفير أثناء السكون (وكسرّ ملف تعريف الارتباط الخاص بالجلسة، المشتقّ منه). يُحفَظ هذا المسار لأغراض التوافق مع الإصدارات السابقة ولكنه **مهمل (موقوف الدعم)** — عيِّن متغير `ENCRYPTION_KEY` مخصَّصاً واتبع إجراء التدوير الموضَّح أعلاه للانتقال بعيداً عنه. يبقى `APP_SECRET` نفسه قيد الاستخدام للتحقق من رموز الوصول HS256 القديمة.
