مانند اکثر نرمافزارهای رمزگذاری دیسک و فایل، رمزگذاری حافظه اندروید به طور سنتی به کلیدهای رمزگذاری خام موجود در حافظه سیستم متکی است تا رمزگذاری بتواند انجام شود. حتی زمانی که رمزگذاری توسط سختافزار اختصاصی به جای نرمافزار انجام میشود، نرمافزار عموماً همچنان نیاز به مدیریت کلیدهای رمزگذاری خام دارد.
این موضوع به طور سنتی به عنوان یک مشکل تلقی نمیشود زیرا کلیدها در طول حمله آفلاین وجود ندارند، که نوع اصلی حملهای است که رمزگذاری ذخیرهسازی برای محافظت در برابر آن در نظر گرفته شده است. با این حال، تمایل به ارائه محافظت بیشتر در برابر انواع دیگر حملات، مانند حملات بوت سرد و حملات آنلاین وجود دارد که در آن یک مهاجم ممکن است بتواند حافظه سیستم را بدون به خطر انداختن کامل دستگاه، نشت دهد.
برای حل این مشکل، اندروید ۱۱ پشتیبانی از کلیدهای سختافزاری را معرفی کرد، که در آن پشتیبانی سختافزاری وجود دارد. کلیدهای سختافزاری، کلیدهای ذخیرهسازی هستند که به صورت خام فقط برای سختافزار اختصاصی شناخته میشوند؛ نرمافزار این کلیدها را فقط به صورت رمزگذاری شده (رمزگذاری شده) میبیند و با آنها کار میکند. این سختافزار باید قادر به تولید و وارد کردن کلیدهای ذخیرهسازی، قرار دادن کلیدهای ذخیرهسازی در قالبهای موقت و بلندمدت، استخراج زیرکلیدها، برنامهنویسی مستقیم یک زیرکلیدها در یک موتور رمزنگاری درونخطی و بازگرداندن یک زیرکلیدی جداگانه به نرمافزار باشد.
توجه: یک موتور رمزنگاری درونخطی (یا سختافزار رمزگذاری درونخطی ) به سختافزاری اشاره دارد که دادهها را در حین انتقال به/از دستگاه ذخیرهسازی، رمزگذاری/رمزگشایی میکند. معمولاً این یک کنترلکننده میزبان UFS یا eMMC است که افزونههای رمزنگاری تعریفشده توسط مشخصات JEDEC مربوطه را پیادهسازی میکند.
طراحی
این بخش، طراحی ویژگی کلیدهای سختافزاری پیچیدهشده، از جمله پشتیبانی سختافزاری مورد نیاز برای آن را ارائه میدهد. این بحث بر رمزگذاری مبتنی بر فایل (FBE) تمرکز دارد، اما این راهحل برای رمزگذاری فراداده نیز صدق میکند.
یک راه برای جلوگیری از نیاز به کلیدهای رمزگذاری خام در حافظه سیستم، نگه داشتن آنها فقط در اسلاتهای کلید یک موتور رمزنگاری درونخطی است. با این حال، این رویکرد با برخی مشکلات مواجه است:
- تعداد کلیدهای رمزگذاری ممکن است از تعداد شیارهای کلید بیشتر باشد.
- موتورهای رمزنگاری درونخطی معمولاً در صورت تنظیم مجدد کنترلکننده میزبان ذخیرهسازی، محتویات اسلاتهای کلید خود را از دست میدهند. تنظیم مجدد کنترلکننده میزبان ذخیرهسازی یک رویه بازیابی خطای استاندارد است که در صورت بروز انواع خاصی از خطاهای ذخیرهسازی اجرا میشود و چنین خطاهایی میتوانند در هر زمانی رخ دهند. بنابراین، هنگامی که از رمزنگاری درونخطی استفاده میشود، سیستم عامل باید همیشه آماده برنامهریزی مجدد اسلاتهای کلید بدون دخالت کاربر باشد.
- موتورهای رمزنگاری درونخطی فقط میتوانند برای رمزگذاری/رمزگشایی کل بلوکهای داده روی دیسک استفاده شوند. با این حال، در مورد FBE، نرمافزار هنوز باید بتواند کارهای رمزنگاری دیگری مانند رمزگذاری نام فایلها و استخراج شناسههای کلید را انجام دهد. نرمافزار همچنان برای انجام این کار دیگر به کلیدهای خام FBE نیاز دارد.
برای جلوگیری از این مشکلات، کلیدهای ذخیرهسازی به کلیدهای سختافزاری پیچیدهشده تبدیل میشوند که میتوانند باز شده و فقط توسط سختافزار اختصاصی مورد استفاده قرار گیرند. این امر امکان پشتیبانی از تعداد نامحدودی از کلیدها را فراهم میکند. علاوه بر این، سلسله مراتب کلید اصلاح شده و تا حدی به این سختافزار منتقل میشود که به یک زیرکلید اجازه میدهد برای کارهایی که نمیتوانند از موتور رمزنگاری درونخطی استفاده کنند، به نرمافزار بازگردانده شود.
سلسله مراتب کلید
کلیدها را میتوان با استفاده از یک تابع مشتقگیری کلید (KDF) مانند HKDF از کلیدهای دیگر استخراج کرد و در نتیجه یک سلسله مراتب کلید ایجاد کرد.
نمودار زیر یک سلسله مراتب کلید معمولی برای FBE را در زمانی که از کلیدهای سختافزاری استفاده نمیشود ، نشان میدهد:
کلید کلاس FBE، کلید رمزگذاری خامی است که اندروید برای باز کردن قفل مجموعهای خاص از دایرکتوریهای رمزگذاری شده، مانند فضای ذخیرهسازی رمزگذاری شده با اعتبارنامه برای یک کاربر خاص اندروید، به هسته لینوکس منتقل میکند. (در هسته، این کلید ، کلید اصلی fscrypt نامیده میشود.) هسته از این کلید، زیرکلیدهای زیر را استخراج میکند:
- شناسه کلید. این برای رمزگذاری استفاده نمیشود، بلکه مقداری است که برای شناسایی کلیدی که یک فایل یا دایرکتوری خاص با آن محافظت میشود، استفاده میشود.
- کلید رمزگذاری محتویات فایل
- کلید رمزگذاری نام فایلها
در مقابل، نمودار زیر سلسله مراتب کلید برای FBE را هنگام استفاده از کلیدهای سختافزاری نشان میدهد:
در مقایسه با مورد قبلی، یک سطح اضافی به سلسله مراتب کلید اضافه شده است و کلید رمزگذاری محتوای فایل جابجا شده است. گره ریشه هنوز نشان دهنده کلیدی است که اندروید برای باز کردن مجموعهای از دایرکتوریهای رمزگذاری شده به لینوکس میدهد. با این حال، اکنون آن کلید به شکل ephemerally-wrapped است و برای استفاده باید به سختافزار اختصاصی منتقل شود. این سختافزار باید دو رابط را پیادهسازی کند که یک کلید ephemerally-wrapped را دریافت میکنند:
- یک رابط برای استخراج
inline_encryption_keyو برنامهریزی مستقیم آن در یک keyslot از موتور رمزنگاری درونخطی. این امر امکان رمزگذاری/رمزگشایی محتویات فایل را بدون دسترسی نرمافزار به کلید خام فراهم میکند. در هستههای رایج اندروید، این رابط مربوط به عملیاتblk_crypto_ll_ops::keyslot_programاست که باید توسط درایور ذخیرهسازی پیادهسازی شود. - یک رابط برای استخراج و بازگرداندن
sw_secret("رمز نرمافزار" - که قبلاً "راز خام" نامیده میشد) که کلیدی است که لینوکس برای استخراج زیرکلیدها برای هر چیزی غیر از رمزگذاری محتوای فایل استفاده میکند. در هستههای رایج اندروید، این رابط مربوط به عملیاتblk_crypto_ll_ops::derive_sw_secretاست که باید توسط درایور ذخیرهسازی پیادهسازی شود.
برای استخراج inline_encryption_key و sw_secret از کلید ذخیرهسازی خام، سختافزار باید از یک KDF قوی از نظر رمزنگاری استفاده کند. این KDF باید از بهترین شیوههای رمزنگاری پیروی کند؛ باید قدرت امنیتی حداقل ۲۵۶ بیت داشته باشد، یعنی برای هر الگوریتمی که بعداً استفاده میشود، کافی باشد. همچنین باید هنگام استخراج هر نوع زیرکلید از یک برچسب و زمینه متمایز استفاده کند تا تضمین شود که زیرکلیدهای حاصل از نظر رمزنگاری ایزوله هستند، یعنی دانستن یکی از آنها، زیرکلید دیگری را فاش نمیکند. بسط کلید لازم نیست، زیرا کلید ذخیرهسازی خام از قبل یک کلید تصادفی یکنواخت است.
از نظر فنی، هر KDF که الزامات امنیتی را برآورده کند، میتواند مورد استفاده قرار گیرد. با این حال، برای اهداف آزمایش، vts_kernel_encryption_test همان KDF را در نرمافزار پیادهسازی میکند تا متن رمز شده روی دیسک را بازتولید کرده و صحت آن را تأیید کند. برای سهولت آزمایش و اطمینان از استفاده از KDF امن و از قبل بررسی شده، توصیه میکنیم سختافزار، KDF پیشفرضی را که آزمایش آن را بررسی میکند، پیادهسازی کند. برای سختافزاری که از KDF متفاوتی استفاده میکند، برای نحوه پیکربندی آزمایش بر اساس آن، به Test Wrapped Keys مراجعه کنید.
بسته بندی کلید
برای دستیابی به اهداف امنیتی کلیدهای سختافزاری، دو نوع پوشش کلید تعریف شده است:
- بستهبندی زودگذر : سختافزار، کلید خام را با استفاده از کلیدی که به صورت تصادفی در هر بوت تولید میشود و مستقیماً در خارج از سختافزار در معرض دید نیست، رمزگذاری میکند.
- بستهبندی بلندمدت : سختافزار، کلید خام را با استفاده از یک کلید منحصر به فرد و پایدار که درون سختافزار تعبیه شده و مستقیماً در خارج از سختافزار در معرض دید نیست، رمزگذاری میکند.
تمام کلیدهایی که برای باز کردن قفل حافظه به هسته لینوکس ارسال میشوند، به صورت موقت رمزگذاری میشوند. این امر تضمین میکند که اگر مهاجمی بتواند یک کلید در حال استفاده را از حافظه سیستم استخراج کند، آن کلید نه تنها در خارج از دستگاه، بلکه پس از راهاندازی مجدد نیز در دستگاه غیرقابل استفاده خواهد بود.
در عین حال، اندروید همچنان باید بتواند یک نسخه رمزگذاری شده از کلیدها را روی دیسک ذخیره کند تا بتوان آنها را در وهله اول قفلگشایی کرد. کلیدهای خام برای این منظور مناسب هستند. با این حال، مطلوب است که هرگز کلیدهای خام در حافظه سیستم وجود نداشته باشند تا هرگز نتوان آنها را برای استفاده در خارج از دستگاه استخراج کرد، حتی اگر در زمان بوت استخراج شوند. به همین دلیل، مفهوم بستهبندی طولانی مدت تعریف شده است.
برای پشتیبانی از مدیریت کلیدهای پیچیده شده به این دو روش مختلف، سختافزار باید رابطهای زیر را پیادهسازی کند:
- رابطهایی برای تولید و وارد کردن کلیدهای ذخیرهسازی، و بازگرداندن آنها به شکل بستهبندیشدهی بلندمدت. رابط تولید توسط
voldبرای تولید کلیدهای ذخیرهسازی جدید برای استفاده توسط اندروید استفاده میشود. رابط واردات توسطvts_kernel_encryption_testبرای وارد کردن کلیدهای آزمایشی استفاده میشود. - رابطی برای تبدیل یک کلید ذخیرهسازی پیچیدهشدهی بلندمدت به یک کلید ذخیرهسازی پیچیدهشدهی موقت. این رابط توسط
voldوvts_kernel_encryption_testبرای باز کردن قفل ذخیرهسازی استفاده میشود.
الگوریتم بستهبندی کلید، یک جزئیات پیادهسازی است، اما باید از یک AEAD قوی مانند AES-256-GCM با IVهای تصادفی استفاده کند.
تغییرات نرمافزاری مورد نیاز
AOSP در حال حاضر یک چارچوب اولیه برای پشتیبانی از کلیدهای سختافزاری دارد. این شامل پشتیبانی در اجزای فضای کاربری مانند vold و همچنین پشتیبانی هسته لینوکس در blk-crypto ، fscrypt و dm-default-key میشود .
تغییرات هسته لینوکس
درایور هسته لینوکس برای کنترلر ذخیرهسازی دستگاه با پشتیبانی از رمزگذاری درونخطی باید برای پشتیبانی از کلیدهای سختافزاری اصلاح شود.
برای کرنلهای 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 )، تولید، وارد کردن و آمادهسازی کلیدهای سختافزاری از ioctls های هسته لینوکس BLKCRYPTOGENERATEKEY ، BLKCRYPTOIMPORTKEY و BLKCRYPTOPREPAREKEY استفاده میکند. این ioctls ها مربوط به متدهای موجود در struct blk_crypto_ll_ops هستند. درایور ذخیرهسازی این متدها را پیادهسازی میکند و برای انجام عملیات درخواستی با سختافزار key wrapping ارتباط برقرار میکند. برای اطلاعات بیشتر در مورد این ioctls ها، به مستندات هسته لینوکس مراجعه کنید.
این ioctlها در لینوکس ۶.۱۶ اضافه شدند. در دستگاههایی که با راهحل مبتنی بر ioctl راهاندازی نشدند، از راهحل متفاوتی با استفاده از Android KeyMint (یا KeyMaster قبلی) استفاده میشود. راهحل قدیمی ( wrappedkey_v0 ) با هسته اصلی لینوکس یا راهحل فعلی سازگار نیست. راهحل قدیمی از قابلیتهای 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 فرض میکند که سختافزار، KDF ای را پیادهسازی میکند که آن را kdf1 مینامد. این KDF متعلق به خانواده حالت شمارنده KDF های NIST SP 800-108 است و از AES-256-CMAC به عنوان تابع شبه تصادفی استفاده میکند. برای اطلاعات بیشتر در مورد CMAC، به مشخصات CMAC مراجعه کنید. KDF هنگام استخراج هر زیرکلید از زمینهها و برچسبهای خاصی استفاده میکند. سختافزار باید این KDF را پیادهسازی کند، از جمله انتخاب دقیق زمینه، برچسب و قالببندی رشته ورودی ثابت هنگام استخراج هر زیرکلید.
با این حال، vts_kernel_encryption_test همچنین KDF های اضافی kdf2 تا kdf4 را پیاده سازی می کند. اینها به همان اندازه kdf1 ایمن هستند و فقط در انتخاب زمینه ها، برچسب ها و قالب بندی رشته ورودی ثابت متفاوت هستند. آنها فقط برای تطبیق با سخت افزارهای مختلف وجود دارند.
برای دستگاههایی که از KDF متفاوتی استفاده میکنند، ویژگی سیستم ro.crypto.hw_wrapped_keys.kdf را در PRODUCT_VENDOR_PROPERTIES روی نام KDF تعریف شده در کد منبع تست تنظیم کنید. این باعث میشود vts_kernel_encryption_test به جای kdf1 ، آن KDF را بررسی کند. به عنوان مثال، برای انتخاب kdf2 ، از دستور زیر استفاده کنید:
PRODUCT_VENDOR_PROPERTIES += ro.crypto.hw_wrapped_keys.kdf=kdf2
برای دستگاههایی که از KDF استفاده میکنند که تست از آن پشتیبانی نمیکند، پیادهسازی آن KDF را نیز به تست اضافه کنید و یک نام منحصر به فرد به آن بدهید.
فعال کردن کلیدهای پیچیده شده
وقتی پشتیبانی از کلید سختافزاری دستگاه به درستی کار میکند، تغییرات زیر را در فایل fstab دستگاه ایجاد کنید تا اندروید از آن برای رمزگذاری FBE و فراداده استفاده کند:
- FBE: پرچم
wrappedkey(یاwrappedkey_v0برای نسخه قدیمی) را به پارامترfileencryptionاضافه کنید. برای مثال، ازfileencryption=::inlinecrypt_optimized+wrappedkeyاستفاده کنید. برای جزئیات بیشتر، به مستندات FBE مراجعه کنید. - رمزگذاری فراداده: پرچم
wrappedkey(یاwrappedkey_v0برای نسخه قدیمی) را به پارامترmetadata_encryptionاضافه کنید. برای مثال،metadata_encryption=:wrappedkeyاستفاده کنید. برای جزئیات بیشتر، به مستندات رمزگذاری فراداده مراجعه کنید.
در هر مورد، دو نسخه از پرچم وجود دارد:
-
wrappedkeyکه توسط اندروید ۱۷ و بالاتر پشتیبانی میشود، نسخه فعلی hardware-wrapped keys را فعال میکند. این نسخه با هسته اصلی لینوکس سازگار است. -
wrappedkey_v0که توسط اندروید ۱۱ و بالاتر پشتیبانی میشود، نسخه قدیمی کلیدهای سختافزاری را فعال میکند. این نسخه با هسته اصلی لینوکس سازگار نیست. این نسخه عملیات خاصی را از طریق KeyMint پروکسی میکند و از یک فرمت غیراستاندارد روی دیسک استفاده میکند. برای اطلاعات بیشتر، به تغییرات KeyMint (قدیمی) مراجعه کنید.
در دستگاههایی که با اندروید ۱۷ یا بالاتر عرضه میشوند، wrappedkey ترجیح دهید.
در دستگاههایی که از قبل با wrappedkey_v0 راهاندازی شدهاند، برای سازگاری با نسخههای قبلی، همچنان wrappedkey_v0 استفاده کنید.