على غرار معظم برامج تشفير الأقراص والملفات، يعتمد تشفير مساحة التخزين في Android تقليديًا على توفّر مفاتيح التشفير الأولية في ذاكرة النظام لإجراء عملية التشفير. حتى عندما يتم إجراء عملية التشفير بواسطة أجهزة مخصّصة بدلاً من البرامج، لا تزال البرامج بحاجة بشكل عام إلى إدارة مفاتيح التشفير الأولية.
لا يُنظر إلى ذلك تقليديًا على أنّه مشكلة لأنّ المفاتيح لا تكون متوفّرة أثناء الهجوم بلا إنترنت، وهو النوع الرئيسي من الهجمات التي يهدف تشفير مساحة التخزين إلى الحماية منها. ومع ذلك، هناك رغبة في توفير حماية أكبر ضد أنواع أخرى من الهجمات، مثل هجمات إعادة التشغيل البارد والهجماتعلى الإنترنت التي قد يتمكّن فيها المهاجم من تسرُّب ذاكرة النظام بدون اختراق الجهاز بالكامل.
لحلّ هذه المشكلة، قدّم Android 11 دعمًا لـ المفاتيح المغلّفة بالأجهزة، حيث يتوفّر دعم الأجهزة. المفاتيح المغلّفة بالأجهزة هي مفاتيح مساحة التخزين التي لا تعرفها البرامج إلا في شكلها المغلّف (المشفّر)، بينما تعرفها الأجهزة المخصّصة في شكلها الأولي فقط. يجب أن تكون هذه الأجهزة قادرة على إنشاء مفاتيح مساحة التخزين واستيرادها، وتغليف مفاتيح مساحة التخزين في أشكال مؤقتة وطويلة الأجل، واستنباط المفاتيح الفرعية، وبرمجة أحد المفاتيح الفرعية مباشرةً في محرّك تشفير مضمّن، وإرجاع مفتاح فرعي منفصل إلى البرامج.
ملاحظة: يشير محرّك التشفير المضمّن (أو أجهزة التشفير المضمّنة) إلى الأجهزة التي تشفّر البيانات أو تفك تشفيرها أثناء انتقالها من جهاز التخزين وإليه. عادةً ما يكون ذلك وحدة تحكّم مضيفة UFS أو eMMC تنفّذ إضافات التشفير المحدّدة في مواصفات JEDEC المقابلة.
تصميم
يعرض هذا القسم تصميم ميزة "المفاتيح المغلّفة بالأجهزة"، بما في ذلك دعم الأجهزة المطلوب لها. يركّز هذا النقاش على التشفير على مستوى الملفات (FBE)، ولكن ينطبق الحلّ أيضًا على تشفير البيانات الوصفية.
إحدى الطرق لتجنُّب الحاجة إلى مفاتيح التشفير الأولية في ذاكرة النظام هي الاحتفاظ بها في فتحات المفاتيح الخاصة بمحرّك تشفير مضمّن فقط. ومع ذلك، يواجه هذا النهج بعض المشاكل:
- قد يتجاوز عدد مفاتيح التشفير عدد فتحات المفاتيح.
- عادةً ما تفقد محرّكات التشفير المضمّنة محتويات فتحات المفاتيح إذا تمت إعادة ضبط وحدة التحكّم المضيفة لمساحة التخزين. إعادة ضبط وحدة التحكّم المضيفة لمساحة التخزين هي إجراء قياسي لاسترداد الأخطاء يتم تنفيذه في حال حدوث أنواع معيّنة من أخطاء مساحة التخزين ، ويمكن أن تحدث هذه الأخطاء في أي وقت. لذلك، عند استخدام التشفير المضمّن ، يجب أن يكون نظام التشغيل جاهزًا دائمًا لإعادة برمجة فتحات المفاتيح بدون تدخّل من المستخدم.
- لا يمكن استخدام محرّكات التشفير المضمّنة إلا لتشفير/فك تشفير كتل البيانات الكاملة من البيانات على القرص. ومع ذلك، في حالة التشفير على مستوى الملفات، لا تزال البرامج بحاجة إلى إجراء أعمال تشفير أخرى، مثل تشفير أسماء الملفات واستنباط معرّفات المفاتيح. ستظل البرامج بحاجة إلى الوصول إلى مفاتيح التشفير على مستوى الملفات الأولية لإجراء هذا العمل الآخر.
لتجنُّب هذه المشاكل، يتم بدلاً من ذلك تحويل مفاتيح مساحة التخزين إلى مفاتيح مغلّفة بالأجهزة، لا يمكن فك تغليفها واستخدامها إلا بواسطة أجهزة مخصّصة. يسمح ذلك بدعم عدد غير محدود من المفاتيح. بالإضافة إلى ذلك، يتم تعديل التسلسل الهرمي للمفاتيح ونقله جزئيًا إلى هذه الأجهزة، ما يسمح بإرجاع مفتاح فرعي إلى البرامج للمهام التي لا يمكنها استخدام محرّك تشفير مضمّن.
التسلسل الهرمي للمفاتيح
يمكن استنباط المفاتيح من مفاتيح أخرى باستخدام دالة استنباط المفاتيح (KDF)، مثل HKDF، ما يؤدي إلى تسلسل هرمي للمفاتيح.
يوضّح الرسم البياني التالي تسلسلاً هرميًا نموذجيًا للمفاتيح في التشفير على مستوى الملفات عندما لا يتم استخدام المفاتيح المغلّفة بالأجهزة:
مفتاح فئة التشفير على مستوى الملفات هو مفتاح التشفير الأولي الذي يمرّره Android إلى نواة Linux لفك تشفير مجموعة معيّنة من الأدلة المشفّرة، مثل مساحة التخزين المشفّرة باستخدام بيانات الاعتماد لمستخدم Android معيّن. (في النواة، يُعرف هذا المفتاح باسم المفتاح الرئيسي لـ fscrypt). من هذا المفتاح، تستنبط النواة المفاتيح الفرعية التالية:
- معرّف المفتاح لا يُستخدم هذا المعرّف للتشفير، بل هو قيمة تُستخدم لتحديد المفتاح الذي تتم حماية ملف أو دليل معيّن به.
- مفتاح تشفير محتويات الملف
- مفتاح تشفير أسماء الملفات
في المقابل، يوضّح الرسم البياني التالي التسلسل الهرمي للمفاتيح في التشفير على مستوى الملفات عند استخدام المفاتيح المغلّفة بالأجهزة:
مقارنةً بالحالة السابقة، تمت إضافة مستوى إضافي إلى التسلسل الهرمي للمفاتيح، وتم نقل مفتاح تشفير محتويات الملف. لا تزال العقدة الجذرية تمثّل المفتاح الذي يمرّره Android إلى Linux لفك تشفير مجموعة من الأدلة المشفّرة. ومع ذلك، يكون هذا المفتاح الآن في شكل مغلّف مؤقتًا، ويجب تمريره إلى أجهزة مخصّصة لاستخدامه. يجب أن تنفّذ هذه الأجهزة واجهتَين تأخذان مفتاحًا مغلّفًا مؤقتًا:
- واجهة لاستنباط
inline_encryption_keyوبرمجته مباشرةً في فتحة مفاتيح محرّك التشفير المضمّن. يسمح ذلك بتشفير/فك تشفير محتويات الملفات بدون أن تتمكّن البرامج من الوصول إلى المفتاح الأولي. في النواة الشائعة لنظام Android، تتوافق هذه الواجهة مع الـblk_crypto_ll_ops::keyslot_programعملية، التي يجب أن يتم تنفيذها بواسطة برنامج تشغيل مساحة التخزين. - واجهة لاستنباط
sw_secretوإرجاعها ("السرّ البرمجي" -- كان يُعرف سابقًا باسم "السرّ الأولي")، وهو المفتاح الذي يستخدمه Linux لاستنباط المفاتيح الفرعية لكل شيء باستثناء تشفير محتويات الملفات. في النواة الشائعة لنظام Android، تتوافق هذه الواجهة مع الـblk_crypto_ll_ops::derive_sw_secretعملية، التي يجب أن يتم تنفيذها بواسطة برنامج تشغيل مساحة التخزين.
لاستنباط inline_encryption_key وsw_secret من مفتاح مساحة التخزين الأولي، يجب أن تستخدم الأجهزة دالة استنباط مفاتيح قوية من ناحية التشفير. يجب أن تتّبع دالة استنباط المفاتيح هذه أفضل ممارسات التشفير، ويجب أن تكون قوة الأمان فيها 256 بت على الأقل، أي ما يكفي لأي خوارزمية مستخدَمة لاحقًا. يجب أيضًا استخدام تصنيف وسياق مميّزَين عند استنباط كل نوع من المفاتيح الفرعية لضمان عزل المفاتيح الفرعية الناتجة من ناحية التشفير، أي أنّ معرفة أحدها لا تكشف أي مفتاح آخر. لا يلزم تمديد المفتاح، لأنّ مفتاح مساحة التخزين الأولي هو مفتاح عشوائي موحّد.
من الناحية الفنية، يمكن استخدام أي دالة استنباط مفاتيح تستوفي متطلبات الأمان.
ومع ذلك، لأغراض الاختبار، vts_kernel_encryption_test
تنفيذ دالة استنباط المفاتيح نفسها في البرامج لإعادة إنتاج النص المشفّر على القرص
والتحقّق من أنّه صحيح. لتسهيل الاختبار وضمان استخدام دالة استنباط مفاتيح آمنة ومراجَعة من قبل، ننصح بأن تنفّذ الأجهزة دالة استنباط المفاتيح التلقائية التي يتحقّق منها الاختبار. بالنسبة إلى الأجهزة التي تستخدم دالة استنباط مفاتيح مختلفة،
يمكنك الاطّلاع على اختبار المفاتيح المغلّفة لمعرفة كيفية ضبط الاختبار
وفقًا لذلك.
تغليف المفاتيح
لتحقيق أهداف الأمان الخاصة بالمفاتيح المغلّفة بالأجهزة، يتم تحديد نوعَين من تغليف المفاتيح:
- التغليف المؤقت: تشفّر الأجهزة المفتاح الأولي باستخدام مفتاح يتم إنشاؤه عشوائيًا في كل عملية تشغيل ولا يتم عرضه مباشرةً خارج الأجهزة.
- التغليف طويل الأجل: تشفّر الأجهزة المفتاح الأولي باستخدام مفتاح فريد وثابت مضمّن في الأجهزة ولا يتم عرضه مباشرةً خارج الأجهزة.
جميع المفاتيح التي يتم تمريرها إلى نواة Linux لفك تشفير مساحة التخزين تكون مغلّفة مؤقتًا. يضمن ذلك أنّه إذا تمكّن مهاجم من استخراج مفتاح قيد الاستخدام من ذاكرة النظام، لن يكون هذا المفتاح قابلاً للاستخدام ليس فقط خارج الجهاز، بل أيضًا على الجهاز بعد إعادة التشغيل.
في الوقت نفسه، لا يزال Android بحاجة إلى أن يكون قادرًا على تخزين نسخة مشفّرة من المفاتيح على القرص حتى يمكن فك تشفيرها في المقام الأول. ستكون المفاتيح الأولية مناسبة لهذا الغرض. ومع ذلك، من المستحسن ألا تكون المفاتيح الأولية موجودة في ذاكرة النظام على الإطلاق حتى لا يمكن استخراجها لاستخدامها خارج الجهاز، حتى إذا تم استخراجها في وقت التشغيل. لهذا السبب، يتم تحديد مفهوم التغليف طويل الأجل.
لدعم إدارة المفاتيح المغلّفة بهاتَين الطريقتَين المختلفتَين، يجب أن تنفّذ الأجهزة الواجهات التالية:
- واجهات لإنشاء مفاتيح مساحة التخزين واستيرادها، وإرجاعها في
شكل مغلّف طويل الأجل. تستخدم
voldواجهة الإنشاء لإنشاء مفاتيح مساحة تخزين جديدة لاستخدامها في Android. تستخدمvts_kernel_encryption_testواجهة الاستيراد لاستيراد مفاتيح الاختبار. - واجهة لتحويل مفتاح مساحة تخزين مغلّف طويل الأجل إلى مفتاح مساحة تخزين مغلّف مؤقتًا. تستخدم كل من
voldوvts_kernel_encryption_testهذه الواجهة لفك تشفير مساحة التخزين.
خوارزمية تغليف المفاتيح هي تفصيل خاص بالتنفيذ، ولكن يجب أن تستخدم خوارزمية تشفير مصادقة مع بيانات إضافية (AEAD) قوية، مثل AES-256-GCM مع متجهات تهيئة عشوائية.
التغييرات المطلوبة في البرامج
يتضمّن AOSP حاليًا إطارًا أساسيًا لدعم المفاتيح المغلّفة بالأجهزة. ويشمل ذلك الدعم في مكوّنات مساحة المستخدم، مثل vold، بالإضافة
إلى دعم نواة Linux في blk-crypto و fscrypt و
dm-default-key.
تغييرات نواة Linux
يجب تعديل برنامج تشغيل نواة Linux لوحدة التحكّم في مساحة تخزين الجهاز التي تتوافق مع التشفير المضمّن لدعم المفاتيح المغلّفة بالأجهزة.
بالنسبة إلى نواة android17 والإصدارات الأحدث:
- اضبط
BLK_CRYPTO_KEY_TYPE_HW_WRAPPEDفيblk_crypto_profile::key_types_supported. - اجعل
blk_crypto_ll_ops::keyslot_programمتوافقة مع برمجة المفاتيح المغلّفة بالأجهزة. - اجعل
blk_crypto_ll_ops::keyslot_evictمتوافقة مع إزالة المفاتيح المغلّفة بالأجهزة. - نفِّذ
blk_crypto_ll_ops::derive_sw_secretوblk_crypto_ll_ops::import_keyوblk_crypto_ll_ops::generate_keyوblk_crypto_ll_ops::prepare_key.
بالنسبة إلى نواة android14 وandroid15 وandroid16:
- اضبط
BLK_CRYPTO_KEY_TYPE_HW_WRAPPEDفيblk_crypto_profile::key_types_supported. - اجعل
blk_crypto_ll_ops::keyslot_programمتوافقة مع برمجة المفاتيح المغلّفة بالأجهزة. - اجعل
blk_crypto_ll_ops::keyslot_evictمتوافقة مع إزالة المفاتيح المغلّفة بالأجهزة. - نفِّذ
blk_crypto_ll_ops::derive_sw_secret.
بالنسبة إلى نواة android12 وandroid13:
- اضبط
BLK_CRYPTO_FEATURE_WRAPPED_KEYSفيblk_keyslot_manager::features. - اجعل
blk_ksm_ll_ops::keyslot_programمتوافقة مع برمجة المفاتيح المغلّفة بالأجهزة. - اجعل
blk_ksm_ll_ops::keyslot_evictمتوافقة مع إزالة المفاتيح المغلّفة بالأجهزة. - نفِّذ
blk_ksm_ll_ops::derive_raw_secret.
بالنسبة إلى نواة android11:
- اضبط
BLK_CRYPTO_FEATURE_WRAPPED_KEYSفيkeyslot_manager::features. - اجعل
keyslot_mgmt_ll_ops::keyslot_programمتوافقة مع برمجة المفاتيح المغلّفة بالأجهزة. - اجعل
keyslot_mgmt_ll_ops::keyslot_evictمتوافقة مع إزالة المفاتيح المغلّفة بالأجهزة. - نفِّذ
keyslot_mgmt_ll_ops::derive_raw_secret.
تغييرات KeyMint (الإصدار القديم)
في الإصدار الحالي من المفاتيح المغلّفة بالأجهزة (wrappedkey)، تستخدم عمليات إنشاء المفاتيح المغلّفة بالأجهزة واستيرادها وإعدادها أوامر ioctl لنواة Linux، وهي BLKCRYPTOGENERATEKEY وBLKCRYPTOIMPORTKEY وBLKCRYPTOPREPAREKEY. تتوافق أوامر ioctl هذه مع الطرق في struct blk_crypto_ll_ops. ينفّذ برنامج تشغيل مساحة التخزين هذه الطرق ويتواصل مع أجهزة تغليف المفاتيح لإجراء العملية المطلوبة. لمزيد من المعلومات عن أوامر ioctl هذه، يمكنك الاطّلاع على مستندات نواة Linux.
تمت إضافة أوامر ioctl هذه في Linux 6.16. على الأجهزة التي لم يتم إطلاقها باستخدام الحلّ المستند إلى أوامر ioctl، يتم استخدام حلّ مختلف يستخدِم Android KeyMint (أو KeyMaster سابقًا). لا يتوافق الحلّ القديم (wrappedkey_v0) مع نواة Linux الرئيسية أو مع الحلّ الحالي. يستخدم الحلّ القديم وظائف KeyMint التالية:
- دعم
TAG_STORAGE_KEY، لكل من إنشاء المفاتيح واستيرادها. - دعم طريقة
convertStorageKeyToEphemeral.
لا تكون وظائف KeyMint هذه مطلوبة إلا على الأجهزة التي تستخدم الحلّ القديم، الذي يتوافق مع wrappedkey_v0 في ملف fstab.
لا تحتاج الأجهزة التي تستخدم الحلّ الحالي، الذي يتوافق مع wrappedkey في ملف fstab، إلى تنفيذ وظائف KeyMint هذه.
اختبار المفاتيح المغلّفة
على الرغم من أنّ اختبار التشفير باستخدام المفاتيح المغلّفة بالأجهزة أصعب من اختبار التشفير باستخدام المفاتيح الأولية، لا يزال من الممكن إجراء الاختبار عن طريق استيراد مفتاح اختبار وإعادة تنفيذ عملية استنباط المفاتيح التي تجريها الأجهزة. تم تنفيذ ذلك
في vts_kernel_encryption_test. لتشغيل هذا الاختبار، نفِّذ الأمر التالي:
atest -v vts_kernel_encryption_test
اقرأ سجلّ الاختبار وتأكّد من أنّه لم يتم تخطّي حالات اختبار المفاتيح المغلّفة بالأجهزة (مثل FBEPolicyTest.TestAesInlineCryptOptimizedHwWrappedKeyPolicy وDmDefaultKeyTest.TestHwWrappedKey) بسبب عدم رصد دعم المفاتيح المغلّفة بالأجهزة، لأنّ نتائج الاختبار ستظل "ناجحة" في هذه الحالة.
تفترض vts_kernel_encryption_test تلقائيًا أنّ الأجهزة تنفّذ دالة استنباط مفاتيح تسمّيها kdf1. تنتمي دالة استنباط المفاتيح هذه
إلى عائلة دوال استنباط المفاتيح في وضع العداد من NIST
SP 800-108، وتستخدم AES-256-CMAC كدالة شبه عشوائية. لمزيد من
المعلومات عن CMAC، يمكنك الاطّلاع على مواصفات
CMAC. تستخدم دالة استنباط المفاتيح سياقات وتصنيفات محدّدة عند استنباط كل مفتاح فرعي. يجب أن تنفّذ الأجهزة دالة استنباط المفاتيح هذه، بما في ذلك الاختيار الدقيق للسياق والتصنيف وتنسيق سلسلة الإدخال الثابتة عند استنباط كل مفتاح فرعي.
ومع ذلك، تنفّذ vts_kernel_encryption_test أيضًا دوال استنباط مفاتيح إضافية، وهي kdf2 إلى kdf4. تكون هذه الدوال آمنة تمامًا مثل kdf1 ولا تختلف إلا في اختيار السياقات والتصنيفات وتنسيق سلسلة الإدخال الثابتة. وهي موجودة فقط لاستيعاب الأجهزة المختلفة.
بالنسبة إلى الأجهزة التي تستخدم دالة استنباط مفاتيح مختلفة، اضبط خاصية النظام ro.crypto.hw_wrapped_keys.kdf في PRODUCT_VENDOR_PROPERTIES على اسم دالة استنباط المفاتيح كما هو محدّد في رمز الاختبار المصدر. يؤدي ذلك إلى أن تتحقّق vts_kernel_encryption_test من دالة استنباط المفاتيح هذه بدلاً من kdf1. على سبيل المثال، لاستخدام kdf2، استخدِم ما يلي:
PRODUCT_VENDOR_PROPERTIES += ro.crypto.hw_wrapped_keys.kdf=kdf2
بالنسبة إلى الأجهزة التي تستخدم دالة استنباط مفاتيح لا يتيحها الاختبار، أضِف أيضًا تنفيذًا لدالة استنباط المفاتيح هذه إلى الاختبار وأعطِها اسمًا فريدًا.
تفعيل المفاتيح المغلّفة
عندما يعمل دعم المفاتيح المغلّفة بالأجهزة بشكل صحيح، أجرِ التغييرات التالية على ملف fstab الخاص بالجهاز لجعل Android يستخدمه في التشفير على مستوى الملفات وتشفير البيانات الوصفية:
- التشفير على مستوى الملفات: أضِف العلامة
wrappedkey(أوwrappedkey_v0للإصدار القديم) إلى المعلمةfileencryption. على سبيل المثال، استخدِمfileencryption=::inlinecrypt_optimized+wrappedkey. لمزيد من التفاصيل، يمكنك الاطّلاع على مستندات التشفير على مستوى الملفات. - تشفير البيانات الوصفية: أضِف العلامة
wrappedkey(أوwrappedkey_v0للإصدار القديم) إلى المعلمةmetadata_encryption. على سبيل المثال، استخدِمmetadata_encryption=:wrappedkey. لمزيد من التفاصيل، يمكنك الاطّلاع على مستندات تشفير البيانات الوصفية.
في كل حالة، هناك إصداران من العلامة:
wrappedkey: يتيح الإصدار الحالي من المفاتيح المغلّفة بالأجهزة، ويتوافق مع Android 17 والإصدارات الأحدث. يتوافق هذا الإصدار مع نواة Linux الرئيسية.wrappedkey_v0، يتيح الإصدار القديم من المفاتيح المغلّفة بالأجهزة، ويتوافق مع Android 11 والإصدارات الأحدث. لا يتوافق هذا الإصدار مع نواة Linux الرئيسية. يُجري هذا الإصدار عمليات معيّنة من خلال KeyMint ويستخدم تنسيقًا غير قياسي على القرص. لمزيد من المعلومات، يمكنك الاطّلاع على تغييرات KeyMint (الإصدار القديم).
على الأجهزة التي يتم إطلاقها باستخدام Android 17 أو الإصدارات الأحدث، يُفضّل استخدام wrappedkey.
على الأجهزة التي تم إطلاقها سابقًا باستخدام wrappedkey_v0، استمر في استخدام wrappedkey_v0 لتحقيق التوافق مع الإصدارات السابقة.