
مكوّن إضافي لتصفية Kroxylicious يوفر تشفيرًا شفافًا بعد-كمي (ML-KEM + AES-256-GCM) على مستوى السجلات لـ Apache Kafka، دون الحاجة إلى أي تغييرات في كود العميل.
مكوّن إضافي لمرشّح Kroxylicious يوفّر تشفيرًا شفّافًا للسجلات على مستوى ما بعد الكم (PQC) لـ Apache Kafka باستخدام تغليف المفاتيح ML-KEM (FIPS 203) مع تشفير تناظري AES-256-GCM.
لا يتطلب منتجو ومستهلكو Kafka أي تغييرات في الكود. يتولّى وكيل Kroxylicious اعتراض حركة المرور وتشفيرها عند Produce / فك تشفيرها عند Fetch تلقائيًا.``` Producer ──plaintext──> Kroxylicious ──encrypted──> Kafka Broker Consumer <──plaintext── Kroxylicious <──encrypted── Kafka Broker
## لماذا PQC لكافكا؟
خوارزميات التأسيس الكلاسيكية للمفاتيح (RSA، ECDH) معرّضة لأجهزة الكمبيوتر الكمومية المستقبلية. إذا تم تأسيس مفاتيح تشفير لكل سجل باستخدام آلية تغليف مفاتيح كلاسيكية (KEM)، فإن خصماً كمومياً قد يتمكن من استعادة هذه المفاتيح من التغليفات المخزّنة بجانب النص المشفر على الوسيط (broker).
يستخدم هذا الملحق ML-KEM (FIPS 203) لتغليف المفاتيح المقاوم للكموم، مما يضمن عدم قدرة أي خصم يمتلك حاسوباً كمومياً ذا أهمية تشفيرية على فك تشفير البيانات المخزّنة على وسيط كافكا (Kafka broker).
**ملاحظة:** يحمي هذا الفلتر **البيانات المخزّنة على الوسيط**، وليس قناة TLS أثناء النقل. راجع [THREAT_MODEL.md](https://github.com/oscerd/kroxylicious-pqc-filter/blob/main/THREAT_MODEL.md) للحصول على تحليل كامل لما يتم الدفاع عنه وما لا يتم الدفاع عنه.
| Standard | Algorithm | Purpose in this plugin |
|----------|-----------|------------------------|
| FIPS 203 | ML-KEM (Kyber) | تغليف المفاتيح - يؤسس بشكل آمن مفتاح AES لكل رسالة |
| N/A | AES-256-GCM | تشفير متماثل موثّق لحمولة السجل |
| N/A | X25519 ECDH | اتفاق مفاتيح كلاسيكي للوضع الهجين لتحقيق الدفاع في العمق |
## الميزات
- **تشفير/فك تشفير شفاف** - لا يتطلب أي تغييرات من جانب العميل
- **مجموعات معاملات ML-KEM-512 و ML-KEM-768 (الافتراضي) و ML-KEM-1024**
- **الوضع الهجين** (الافتراضي) - يجمع بين ML-KEM + X25519 ECDH بحيث يجب كسر الاثنين معًا
- **تشفير لكل سجل** - يحصل كل سجل على تغليف KEM جديد + IV عشوائي
- **تصفية المواضيع** - أنماط regex تختار المواضيع التي سيتم تشفيرها
- **كشف العبث** - تشفير AES-GCM الموثّق يرفض النص المشفر المعدّل
- **الأمان الدلالي** - النصوص الواضحة المتطابقة تنتج نصوصًا مشفرة مختلفة (IND-CCA2)
- **التوليد التلقائي للمفاتيح** - يولّد ويحفظ مفاتيح ML-KEM عند أول تشغيل إذا كانت غائبة
- **ترويسة `x-pqc-encrypted`** - تعلّم السجلات المشفرة لتوعية الأنظمة النهائية
- **موفرو مفاتيح قابلون للتوصيل** - يدعم `KeyProvider` SPI أنظمة الملفات (الافتراضي) والخلفيات HashiCorp Vault
## المتطلبات الأساسية
| Requirement | Version |
|-------------|---------|
| JDK | 17+ (يُوصى بـ 21+) |
| Maven | 3.8+ |
| Kroxylicious | 0.19.0 |
| Apache Kafka | 3.9.x |
## بدء سريع
### 1. بناء الإضافة```bash
git clone <this-repo>
cd kroxylicious-pqc-filter
mvn clean package -DskipTests
يُجمّع ملف JAR المُظلَّل (shaded JAR) عند target/kroxylicious-pqc-filter-1.0.0-SNAPSHOT.jar مكتبة Bouncy Castle داخله، بحيث يمكن وضعه مباشرة في Kroxylicious دون أي تبعيات إضافية.
لتضمين دعم مزوّد مفاتيح HashiCorp Vault، قم بالبناء باستخدام ملف التعريف vault:```bash
mvn clean package -Pvault -DskipTests
This bundles `spring-vault-core` and the `VaultKeyProvider` into the JAR.
### 2. توليد مفاتيح ML-KEM```bash
java -cp target/kroxylicious-pqc-filter-1.0.0-SNAPSHOT.jar \
io.kroxylicious.filter.pqc.PqcKeyGeneratorCli \
ML_KEM_768 \
/etc/kroxylicious/pqc/
المخرجات:``` Generating ML-KEM-768 key pair... Public key: /etc/kroxylicious/pqc/pqc-public.der Size: 1206 bytes Format: X.509 Private key: /etc/kroxylicious/pqc/pqc-private.der Size: 2498 bytes Format: PKCS#8
بدلاً من ذلك، احذف مسارات المفاتيح من الإعدادات وسيقوم الفلتر بتوليد
المفاتيح تلقائياً عند أول تشغيل.
### 3. إعداد Kroxylicious
أضف الفلتر إلى إعدادات YAML الخاصة بوكيل Kroxylicious:```yaml
filterDefinitions:
- name: pqc-encryption
type: PqcRecordEncryptionFilterFactory
config:
kemAlgorithm: ML_KEM_768
hybridMode: true
publicKeyPath: /etc/kroxylicious/pqc/pqc-public.der
privateKeyPath: /etc/kroxylicious/pqc/pqc-private.der
topicPatterns:
- "sensitive-.*"
- "pii-.*"
defaultFilters:
- pqc-encryption
ضع ملف JAR في دليل يمكن الوصول إليه من Kroxylicious وأضفه إلى
classpath عبر متغير البيئة KROXYLICIOUS_CLASSPATH:```bash
export KROXYLICIOUS_CLASSPATH="/opt/kroxylicious/plugins/*"
عند استخدام Docker، قم بتعيينه في بيئة الحاوية الخاصة بك:```yaml
environment:
KROXYLICIOUS_CLASSPATH: /opt/kroxylicious/plugins/*
ثم ابدأ البروكسي. يتصل المنتجون والمستهلكون بمنفذ البروكسي بدلاً من الوسيط مباشرة.
نظام الملفات (keyProviderType: filesystem، الافتراضي):
يحمل مفاتيح ML-KEM من ملفات DER على القرص. إذا لم تكن الملفات موجودة، يقوم
بإنشاء زوج مفاتيح جديد وحفظها. يتطلب publicKeyPath و privateKeyPath.
HashiCorp Vault (keyProviderType: vault، يتطلب بناء -Pvault):
يجلب مفاتيح ML-KEM من محرك أسرار KV v2 في Vault. تُخزَّن المفاتيح بصيغة
DER مشفّرة بـ base64 في حقلي publicKey و privateKey. ترتبط إصدارات أسرار Vault
بمعرّفات المفاتيح لدعم تدوير المفاتيح.
خصائص keyProviderConfig الخاصة بـ Vault:
مثال على تكوين Vault:```yaml filterDefinitions:
### مجموعات معلمات ML-KEM
| الخوارزمية | مستوى الأمان | المفتاح العام | المفتاح الخاص | الحمل الإضافي للنص المشفر | حالة الاستخدام |
|-----------|---------------|------------|-------------|--------------------:|----------|
| ML-KEM-512 | 128-bit | 822 B | 1,730 B | ~854 B | خفيف الوزن، إنترنت الأشياء |
| ML-KEM-768 | 192-bit | 1,206 B | 2,498 B | ~1,174 B | **الافتراضي الموصى به** |
| ML-KEM-1024 | 256-bit | 1,590 B | 3,266 B | ~1,654 B | بيانات سرية / طويلة الأمد |
### أوضاع التشفير
**PQC-only** (`hybridMode: false`):
يستخدم ML-KEM حصريًا. يتم اشتقاق مفتاح AES-256 من السر المشترك لـ ML-KEM
عبر `SHA-256(0x01 || "kroxylicious-pqc-v1" || secret)`.
**Hybrid** (`hybridMode: true`, الافتراضي):
يجمع بين ML-KEM وX25519. يتم اشتقاق مفتاح AES-256 من كلا السرّين عبر
`SHA-256(0x02 || "kroxylicious-pqc-hybrid-v1" || pqcSecret || x25519Secret)`.
يضمن ذلك الأمان حتى لو تم كسر إحدى الخوارزميات.
## تنسيق الظرف المشفّر
يتم استبدال كل قيمة سجلّ مشفّرة بظرف ثنائي:```
PQC-only (version 0x01):
+--------+-----------+--------------------+--------+---------------------------+
| 1 byte | 2 bytes | N bytes | 12 B | remaining |
| 0x01 | encap len | ML-KEM encapsulat. | AES IV | AES-GCM ciphertext + tag |
+--------+-----------+--------------------+--------+---------------------------+
Hybrid (version 0x02):
+--------+-----------+--------------------+---------+--------+-----------------+
| 1 byte | 2 bytes | N bytes | 32 B | 12 B | remaining |
| 0x02 | encap len | ML-KEM encapsulat. | X25519 | AES IV | AES-GCM ct+tag |
| | | | eph pub | | |
+--------+-----------+--------------------+---------+--------+-----------------+
يسمح بايت الإصدار لوحدة فك التشفير بتحديد الوضع بدون إعدادات.
kroxylicious-pqc-filter/ src/main/java/io/kroxylicious/filter/pqc/ PqcRecordEncryptionFilterFactory.java # FilterFactory entry point (@Plugin) PqcRecordEncryptionFilter.java # ProduceRequestFilter + FetchResponseFilter PqcKeyGeneratorCli.java # CLI key generation utility config/ PqcEncryptionConfig.java # Jackson-deserialized configuration POJO crypto/ KeyProvider.java # SPI interface for pluggable key backends FileSystemKeyProvider.java # Default: loads/generates keys from disk PqcCryptoEngine.java # ML-KEM encapsulation + AES-256-GCM PqcKeyManager.java # Resolves KeyProvider via ServiceLoader src/main/java-vault/ # (vault profile only) .../crypto/ VaultKeyProvider.java # HashiCorp Vault KV v2 key provider src/main/resources/ META-INF/services/ io.kroxylicious.proxy.filter.FilterFactory # ServiceLoader: filter registration io.kroxylicious.filter.pqc.crypto.KeyProvider # ServiceLoader: key provider src/main/resources-vault/ # (vault profile only) META-INF/services/ io.kroxylicious.filter.pqc.crypto.KeyProvider # Registers both FS + Vault providers src/test/java/... # Unit tests (49 tests) src/test/java-vault/... # Vault provider tests (17 tests) examples/ standalone/ # Runs without Kafka (crypto engine demo) docker/ # Docker Compose: Kafka + Vault + Kroxylicious
### تدفق البيانات
#### ماذا يُخزَّن في Vault
يحتوي Vault KV v2 على زوج مفاتيح ML-KEM في `secret/<secretPath>` (على سبيل المثال، `secret/kroxylicious/pqc`):
| Field | Content | Format |
|-------|---------|--------|
| `publicKey` | مفتاح ML-KEM العام (يُستخدم للتغليف) | مُرمّز Base64 بصيغة X.509 DER |
| `privateKey` | مفتاح ML-KEM الخاص (يُستخدم لفك التغليف) | مُرمّز Base64 بصيغة PKCS#8 DER |
كل إصدار من إصدارات السر في Vault يعمل كمعرّف مفتاح، مما يتيح تدوير المفاتيح. الإصدارات الجديدة
تشفّر سجلات جديدة؛ الإصدارات القديمة لا تزال قادرة على فك تشفير السجلات التي شُفِّرت بها.
#### تدفق بدء التشغيل```
┌─────────────────────────────────────────────────────────────────────┐
│ STARTUP │
│ │
│ 1. Vault starts (dev mode, port 8200) │
│ │ │
│ 2. vault-init container: │
│ │ • Reads pre-generated ML-KEM DER key files from disk │
│ │ • Base64-encodes them │
│ │ • POST /v1/secret/data/kroxylicious/pqc │
│ │ • Stores publicKey + privateKey in Vault KV v2 │
│ │ • Exits │
│ │ │
│ 3. Kafka starts (KRaft mode, port 9092) │
│ │ │
│ 4. Kroxylicious starts (port 9192): │
│ │ │
│ │ FilterFactory.initialize() │
│ │ → PqcKeyManager resolves VaultKeyProvider │
│ │ via ServiceLoader (matches keyProviderType: "vault") │
│ │ → VaultKeyProvider.configure() │
│ │ connects to Vault (token/approle/kubernetes auth) │
│ │ → Fetches ML-KEM key pair from Vault KV v2 │
│ │ (GET /v1/secret/data/kroxylicious/pqc) │
│ │ → Decodes: base64 → DER bytes → Java Key objects │
│ │ → PqcCryptoEngine initialized with key pair │
│ ▼ │
│ Proxy ready — Vault is NOT contacted again per-message │
└─────────────────────────────────────────────────────────────────────┘
Kroxylicious (:9192)
┌───────────────────────────────────────────┐
Producer ──plaintext──> │ PqcRecordEncryptionFilter │ │ .onProduceRequest() │ │ │ │ For each record in matching topic: │ │ 1. ML-KEM encapsulate (public key) │ │ → fresh shared secret │ │ → encapsulation blob │ │ 2. SHA-256(shared secret) → AES-256 key │ │ 3. AES-GCM encrypt record value │ │ 4. Build binary envelope: │ │ [ver|encap_len|encap|IV|ciphertext] │ │ 5. Add x-pqc-encrypted header │ └────────────────┬──────────────────────────┘ │ ▼ Kafka Broker (:9092) stores encrypted blob
#### تدفق الجلب (فك التشفير)```
Kafka Broker (:9092)
returns encrypted blob
│
▼
┌────────────────┴──────────────────────────┐
│ PqcRecordEncryptionFilter │
│ .onFetchResponse() │
│ │
│ For each record with x-pqc-encrypted: │
│ 1. Parse envelope → extract encap + │
│ ciphertext │
│ 2. ML-KEM decapsulate (private key) │
│ → recover shared secret │
│ 3. SHA-256(shared secret) → AES-256 key │
│ 4. AES-GCM decrypt → plaintext │
│ 5. Remove x-pqc-encrypted header │
Consumer <──plaintext── │ │
└───────────────────────────────────────────┘
Kroxylicious (:9192)
Startup: FilterFactory.initialize() -> PqcKeyManager loads/generates ML-KEM key pair -> PqcCryptoEngine created with key pair -> Topic patterns compiled -> SharedPqcContext returned
Per connection: FilterFactory.createFilter() -> New PqcRecordEncryptionFilter instance -> Shares the same PqcCryptoEngine (thread-safe)
Produce request: onProduceRequest() -> For each topic matching topicPatterns: -> For each partition: -> For each record: -> ML-KEM encapsulate (fresh shared secret + encapsulation) -> Derive AES-256 key from shared secret -> AES-GCM encrypt the record value -> Replace value with encrypted envelope -> Add x-pqc-encrypted header -> Forward to broker
Fetch response: onFetchResponse() -> For each topic matching topicPatterns: -> For each partition: -> For each record with x-pqc-encrypted header: -> Read version + encapsulation from envelope -> ML-KEM decapsulate (recover shared secret) -> Derive AES-256 key -> AES-GCM decrypt -> Replace value with plaintext -> Remove x-pqc-encrypted header -> Forward to client
### الفئات الأساسية
**`PqcRecordEncryptionFilterFactory`** تنفّذ `FilterFactory<PqcEncryptionConfig, SharedPqcContext>`.
مُعلَّمة بـ `@Plugin(configType = PqcEncryptionConfig.class)`.
مُسجَّلة عبر `META-INF/services/io.kroxylicious.proxy.filter.FilterFactory`.
تُستدعى مرة واحدة عند بدء التشغيل (`initialize`) ومرة واحدة لكل اتصال عميل (`createFilter`).
**`PqcRecordEncryptionFilter`** تنفّذ `ProduceRequestFilter` و`FetchResponseFilter`.
يعترض `onProduceRequest` للتشفير و`onFetchResponse` لفك التشفير.
مثيل واحد لكل اتصال؛ لا حاجة للمزامنة (نموذج خيوط Kroxylicious).
**`PqcCryptoEngine`** يقوم بجميع العمليات التشفيرية.
بلا حالة باستثناء مواد المفاتيح و`SecureRandom`.
`encrypt()` يُرجع مغلّفًا ذاتي الوصف؛ بينما `decrypt()` يحلّله.
يُسجّل مزوّدي Bouncy Castle (`BC`, `BCPQC`) في مُهيّئ ثابت.
**`PqcKeyManager`** يحلّ `KeyProvider` عبر `ServiceLoader`، بالمطابقة حسب
`keyProviderType`. يفوّض جميع عمليات المفاتيح إلى المزوّد المُحَلّ.
**`KeyProvider`** هي واجهة SPI للواجهات الخلفية القابلة للتوصيل لتخزين المفاتيح.
تُكتشف التطبيقات عبر `META-INF/services`. المزوّدات المدمجة:
`FileSystemKeyProvider` (الافتراضي) و`VaultKeyProvider` (عبر `-Pvault`).
**`PqcEncryptionConfig`** هو POJO مُعلَّم بـ Jackson.
يُفكّ تسلسله من كتلة `config:` في YAML الخاص بوكيل Kroxylicious.
يدعم `keyProviderType` و`keyProviderConfig` لاختيار الواجهة الخلفية.
## البناء والاختبار```bash
# Compile
mvn compile
# Run unit tests (38 core tests)
mvn test
# Package (creates shaded JAR with Bouncy Castle bundled)
mvn package
# Build with Vault support (55 tests: 38 core + 17 vault)
mvn package -Pvault
# Install to local Maven repository
mvn install
الإجمالي: 55 اختبارًا (38 أساسية + 17 لـ Vault)
JUnit 5.11.4, Mockito 5.15.2, AssertJ 3.27.3, Jackson Databind 2.18.3.
تم القياس على العرض التوضيحي المستقل (بعد تسخين JVM، 1,000 تكرار، رسائل بحجم 1 KB):
| الخوارزمية | التشفير | فك التشفير |
|---|---|---|
| ML-KEM-512 | ~7,700 رسالة/ثانية (0.13 ms/رسالة) | ~7,800 رسالة/ثانية (0.13 ms/رسالة) |
الحمل الإضافي لكل رسالة أقل من مللي ثانية واحدة. بالنسبة لأحمال عمل Kafka النموذجية (رسائل في نطاق KB-MB)، تكون تكلفة التشفير ضئيلة مقارنةً بإدخال/إخراج الشبكة والقرص.
للحصول على نموذج تهديدات كامل يغطي ما يحمي منه هذا الفلتر وما لا يحمي منه، راجع THREAT_MODEL.md.
chmod 600).approle أو kubernetes على الرموز الثابتة في بيئة الإنتاج. لا تضع رموز Vault في نظام التحكم بالإصدارات أبدًا.null (شواهد قبور Kafka) تُمرَّر دون تشفير.Compression.NONE لأن البيانات المشفرة لا تنضغط جيدًا.رخصة Apache 2.0. راجع LICENSE للتفاصيل.
| الخاصية | النوع | مطلوب | الافتراضي | الوصف |
|---|
kemAlgorithm | enum | لا | ML_KEM_768 | مجموعة معلمات ML-KEM. واحدة من ML_KEM_512، ML_KEM_768، ML_KEM_1024. |
hybridMode | boolean | لا | true | يجمع ML-KEM مع X25519 ECDH للدفاع المتعمق. |
publicKeyPath | string | نظام الملفات فقط | - | مسار نظام الملفات إلى المفتاح العام ML-KEM (مُرمّز X.509 DER). |
privateKeyPath | string | نظام الملفات فقط | - | مسار نظام الملفات إلى المفتاح الخاص ML-KEM (مُرمّز PKCS#8 DER). |
topicPatterns | list<string> | لا | [".*"] | أنماط regex في Java. فقط السجلات في المواضيع المتطابقة يتم تشفيرها/فك تشفيرها. |
keyProviderType | string | لا | filesystem | الواجهة الخلفية لتخزين المفاتيح. أحد الخيارات filesystem، vault. |
keyProviderConfig | map<string, string> | Vault فقط | {} | إعدادات خاصة بالواجهة الخلفية (انظر قسم Vault أدناه). |
| الخاصية | مطلوب | الافتراضي | الوصف |
|---|
vaultAddress | نعم | VAULT_ADDR متغير بيئة | عنوان URL لخادم Vault (مثل http://vault:8200) |
vaultToken | لمصادقة token | VAULT_TOKEN متغير بيئة | رمز مصادقة Vault |
secretPath | نعم | -- | المسار داخل محرك الأسرار (مثل kroxylicious/pqc) |
secretEngine | لا | secret | اسم نقطة التحميل لمحرك أسرار KV v2 |
authMethod | لا | token | طريقة المصادقة: token، approle، أو kubernetes |
roleId | لـ approle | -- | معرّف دور AppRole |
secretId | لـ approle | -- | معرّف سر AppRole |
kubeRole | لـ kubernetes | -- | اسم دور مصادقة Kubernetes |
kubeTokenPath | لا | /var/run/secrets/.../token | مسار ملف رمز حساب الخدمة |
| فئة الاختبار | الاختبارات | ما الذي يتم التحقق منه |
|---|
PqcCryptoEngineTest | 12 | التشفير/فك التشفير ذهابًا وإيابًا لجميع متغيرات ML-KEM الثلاثة، معالجة القيم الفارغة، حمولات فارغة وحمولات بحجم 1MB، الأمان الدلالي، كشف العبث، رفض الإصدارات غير الصالحة، توليد المفاتيح، بايتات إصدار المغلف |
PqcEncryptionConfigTest | 9 | القيم الافتراضية، القيم الصريحة، رفض القيم الفارغة، إلغاء تسلسل JSON، الثبات، خصائص التعداد، إلغاء تسلسل keyProviderConfig |
PqcKeyManagerTest | 6 | تحديد KeyProvider، إنشاء المحرك، التفويض إلى المزودين، التبديل الاحتياطي لنوع نظام الملفات |
FileSystemKeyProviderTest | 11 | توليد المفاتيح، تحميل المفاتيح الموجودة، معرف المفتاح الافتراضي، رفض معرف المفتاح غير المعروف، التحقق من المسار الفارغ، اكتشاف ServiceLoader، التشفير وفك التشفير ذهابًا وإيابًا |
VaultKeyProviderTest | 17 | التحقق من الإعدادات (مسار/عنوان/رمز/approle/kube مفقود)، جلب المفتاح من Vault، استرجاع المفاتيح المُصدَّرة، المفاتيح المخزنة مؤقتًا، الإصدارات غير الصالحة، الحقول المفقودة في السر، التشفير وفك التشفير ذهابًا وإيابًا، سلوك الإغلاق |
| التبعية | الإصدار | النطاق | الغرض |
|---|
io.kroxylicious:kroxylicious-api | 0.19.0 | provided | واجهات برمجة تطبيقات الفلتر |
org.apache.kafka:kafka-clients | 3.9.0 | provided | أنواع رسائل بروتوكول Kafka |
com.fasterxml.jackson.core:jackson-annotations | 2.18.3 | provided | ربط الإعدادات |
org.bouncycastle:bcprov-jdk18on | 1.83 | compile | ML-KEM وAES-GCM وX25519 (مضمّنة في shaded JAR) |
org.bouncycastle:bcutil-jdk18on | 1.83 | compile | أدوات Bouncy Castle (مضمّنة في shaded JAR) |
org.slf4j:slf4j-api | 2.0.17 | provided | التسجيل |
org.springframework.vault:spring-vault-core | 3.1.2 | compile (ملف تعريف vault) | عميل Vault KV v2 (مضمّن عند البناء باستخدام -Pvault) |
| ML-KEM-768 | ~9,600 رسالة/ثانية (0.10 ms/رسالة) | ~10,200 رسالة/ثانية (0.10 ms/رسالة) |
| ML-KEM-1024 | ~8,500 رسالة/ثانية (0.12 ms/رسالة) | ~6,900 رسالة/ثانية (0.14 ms/رسالة) |