מפתחות שמוגדרים בחומרה

בדומה לרוב תוכנות ההצפנה של דיסקים וקבצים, הצפנת האחסון של Android מסתמכת בדרך כלל על מפתחות הצפנה גולמיים שקיימים בזיכרון המערכת, כדי שניתן יהיה לבצע את ההצפנה. גם כשההצפנה מתבצעת על ידי חומרה ייעודית ולא על ידי תוכנה, בדרך כלל התוכנה עדיין צריכה לנהל את מפתחות ההצפנה הגולמיים.

בדרך כלל זה לא נחשב לבעיה כי המפתחות לא קיימים במהלך מתקפה אופליין, שהיא סוג המתקפה העיקרי שהצפנת האחסון נועדה להגן מפניו. עם זאת, יש רצון לספק הגנה משופרת מפני סוגים אחרים של מתקפות, כמו מתקפות אתחול קר ומתקפות אונליין שבהן התוקף יכול לגנוב זיכרון מערכת בלי לפרוץ למכשיר.

כדי לפתור את הבעיה הזו, ב-Android 11 הוספנו תמיכה במפתחות מוצפנים בחומרה, במקרים שבהם יש תמיכה בחומרה. מפתחות שעטופים בחומרה הם מפתחות אחסון שמוכרים בצורה גולמית רק לחומרה ייעודית. התוכנה רואה את המפתחות האלה ועובדת איתם רק בצורה עטופה (מוצפנת). החומרה הזו צריכה להיות מסוגלת ליצור ולייבא מפתחות אחסון, לעטוף מפתחות אחסון בטפסים זמניים ולטווח ארוך, לגזור מפתחות משנה, לתכנת ישירות מפתח משנה אחד במנוע הצפנה מוטבע ולהחזיר מפתח משנה נפרד לתוכנה.

הערה: מנוע קריפטוגרפי מוטמע (או חומרה להצפנה מוטמעת) הוא חומרה שמצפינה או מפענחת את הנתונים בדרך אל מכשיר האחסון או ממנו. בדרך כלל זהו בקר מארח UFS או eMMC שמטמיע את תוספי ההצפנה שמוגדרים במפרט JEDEC המתאים.

עיצוב

בקטע הזה מוצג העיצוב של התכונה 'מפתחות שעטופים בחומרה', כולל תמיכה בחומרה שנדרשת בשביל התכונה הזו. הדיון הזה מתמקד בהצפנה מבוססת קבצים (FBE), אבל הפתרון רלוונטי גם להצפנת מטא-נתונים.

דרך אחת להימנע מהצורך במפתחות ההצפנה הגולמיים בזיכרון המערכת היא לשמור אותם רק במשבצות המפתחות של מנוע קריפטו מוטבע. עם זאת, לגישה הזו יש כמה בעיות:

  • יכול להיות שמספר מפתחות ההצפנה גדול ממספר משבצות המפתחות.
  • בדרך כלל, מנועי הצפנה מוטבעים מאבדים את התוכן של משבצות המפתחות שלהם אם בקר מארח האחסון מאופס. איפוס של בקר מארח האחסון הוא הליך סטנדרטי לשחזור שגיאות שמופעל אם מתרחשות שגיאות מסוימות באחסון, ושגיאות כאלה יכולות להתרחש בכל שלב. לכן, כשמשתמשים בהצפנה מוטבעת, מערכת ההפעלה צריכה להיות מוכנה תמיד לתכנת מחדש את משבצות המפתחות ללא התערבות של המשתמש.
  • אפשר להשתמש במנועי הצפנה מוטבעים רק כדי להצפין או לפענח בלוקים מלאים של נתונים בדיסק. עם זאת, במקרה של FBE, התוכנה עדיין צריכה להיות מסוגלת לבצע עבודת הצפנה אחרת, כמו הצפנת שמות קבצים וגזירת מזהי מפתחות. התוכנה עדיין תצטרך גישה למפתחות FBE הגולמיים כדי לבצע את העבודה הזו.

כדי להימנע מהבעיות האלה, מפתחות האחסון הופכים למפתחות שעטופים בחומרה, שאפשר לפתוח את העטיפה שלהם ולהשתמש בהם רק באמצעות חומרה ייעודית. כך אפשר לתמוך במספר בלתי מוגבל של מפתחות. בנוסף, היררכיית המפתחות משתנה ומועברת באופן חלקי לחומרה הזו, מה שמאפשר להחזיר מפתח משני לתוכנה למשימות שלא יכולות להשתמש במנוע קריפטוגרפי מוטבע.

היררכיית מפתחות

אפשר לגזור מפתחות ממפתחות אחרים באמצעות פונקציה לגזירת מפתחות (KDF) כמו HKDF, וכך ליצור היררכיית מפתחות.

בתרשים הבא מוצגת היררכיית מפתחות אופיינית להצפנה מבוססת-קבצים (FBE) כשלא נעשה שימוש במפתחות שעטופים בחומרה:

היררכיית מפתחות FBE (רגילה)
איור 1. היררכיית מפתחות FBE (רגילה)

מפתח המחלקה FBE הוא מפתח ההצפנה הגולמי ש-Android מעבירה לליבת Linux כדי לבטל את הנעילה של קבוצה מסוימת של ספריות מוצפנות, כמו האחסון המוצפן של פרטי הכניסה של משתמש Android מסוים. (בליבת המערכת, המפתח הזה נקרא מפתח מאסטר של fscrypt). ממפתח זה, הליבה גוזרת את מפתחות המשנה הבאים:

  • מזהה המפתח. הערך הזה לא משמש להצפנה, אלא לזיהוי המפתח שבאמצעותו קובץ או ספריה מסוימים מוגנים.
  • המפתח להצפנת תוכן הקובץ
  • מפתח ההצפנה של שמות הקבצים

לעומת זאת, בתרשים הבא מוצגת היררכיית המפתחות של FBE כשמשתמשים במפתחות שעברו עטיפה בחומרה:

היררכיית מפתחות FBE (עם מפתח עטוף בחומרה)
איור 2. היררכיית מפתחות FBE (עם מפתח שעטוף בחומרה)

בהשוואה למקרה הקודם, נוספה רמה נוספת להיררכיית המפתחות, והמפתח להצפנת תוכן הקובץ הועבר. צומת הבסיס עדיין מייצג את המפתח ש-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 ממפתח האחסון הגולמי, החומרה צריכה להשתמש ב-KDF חזק מבחינה קריפטוגרפית. ה-KDF הזה צריך לפעול לפי שיטות מומלצות להצפנה, ורמת האבטחה שלו צריכה להיות לפחות 256 ביט, כלומר מספיק לכל אלגוריתם שישמש בהמשך. בנוסף, צריך להשתמש בתווית ובהקשר שונים כשגוזרים כל סוג של מפתח משנה, כדי להבטיח שמפתחות המשנה שמתקבלים מבודדים מבחינה קריפטוגרפית, כלומר, הידע על אחד מהם לא חושף אף אחד אחר. אין צורך בהרחבת מפתח, כי מפתח האחסון הגולמי הוא כבר מפתח אקראי אחיד.

מבחינה טכנית, אפשר להשתמש בכל KDF שעומד בדרישות האבטחה. עם זאת, לצורך בדיקה, vts_kernel_encryption_test מטמיע את אותו KDF בתוכנה כדי לשחזר את הטקסט המוצפן בדיסק ולוודא שהוא נכון. כדי לבצע בדיקות בקלות ולוודא שנעשה שימוש ב-KDF מאובטח שכבר נבדק, מומלץ להטמיע בחומרה את ה-KDF שמוגדר כברירת מחדל ושנבדק במהלך הבדיקה. אם החומרה משתמשת ב-KDF אחר, במאמר בדיקת מפתחות מוצפנים מוסבר איך להגדיר את הבדיקה בהתאם.

עטיפת מפתחות

כדי לעמוד ביעדי האבטחה של מפתחות שעטופים בחומרה, מוגדרים שני סוגים של עטיפת מפתחות:

  • אריזה זמנית: החומרה מצפינה את המפתח הגולמי באמצעות מפתח שנוצר באופן אקראי בכל אתחול ולא נחשף ישירות מחוץ לחומרה.
  • אריזה לטווח ארוך: החומרה מצפינה את המפתח הגולמי באמצעות מפתח ייחודי וקבוע שמוטמע בחומרה ולא נחשף ישירות מחוץ לחומרה.

כל המפתחות שמועברים לליבת Linux כדי לבטל את הנעילה של האחסון הם מפתחות זמניים. כך, אם תוקף יצליח לחלץ מפתח שנמצא בשימוש מזיכרון המערכת, הוא לא יוכל להשתמש במפתח הזה לא רק מחוץ למכשיר, אלא גם במכשיר אחרי הפעלה מחדש.

במקביל, מערכת Android עדיין צריכה להיות מסוגלת לאחסן גרסה מוצפנת של המפתחות בדיסק, כדי שיהיה אפשר לבטל את הנעילה שלהם. המפתחות הגולמיים יתאימו למטרה הזו. עם זאת, מומלץ שהמפתחות הגולמיים לא יהיו בזיכרון המערכת בכלל, כדי שלא יהיה אפשר לחלץ אותם לשימוש מחוץ למכשיר, גם אם הם חולצו בזמן האתחול. לכן, מוגדר המושג 'עטיפה לטווח ארוך'.

כדי לתמוך בניהול מפתחות שעברו הצפנה בשתי הדרכים השונות האלה, החומרה צריכה להטמיע את הממשקים הבאים:

  • ממשקי API ליצירה ולייבוא של מפתחות אחסון, שמוחזרים בצורה עטופה לטווח ארוך. ממשק generate משמש את vold ליצירת מפתחות אחסון חדשים לשימוש ב-Android. ממשק הייבוא משמש את vts_kernel_encryption_test לייבוא מפתחות בדיקה.
  • ממשק להמרה של מפתח אחסון עטוף לטווח ארוך למפתח אחסון עטוף לזמן קצר. הממשק הזה משמש גם את vold וגם את vts_kernel_encryption_test כדי לבטל את הנעילה של האחסון.

אלגוריתם עטיפת המפתח הוא פרט הטמעה, אבל הוא צריך להשתמש ב-AEAD חזק כמו AES-256-GCM עם וקטורים אקראיים של אתחול (IV).

נדרשים שינויים בתוכנה

ב-AOSP כבר יש מסגרת בסיסית לתמיכה במפתחות שעטופים בחומרה. התמיכה הזו כוללת רכיבים במרחב המשתמש כמו vold, וגם תמיכה בליבת לינוקס ב-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), הפעולות של יצירה, ייבוא והכנה של מפתחות שעטופים בחומרה משתמשות ב-ioctls של ליבת Linux‏ BLKCRYPTOGENERATEKEY,‏ BLKCRYPTOIMPORTKEY ו-BLKCRYPTOPREPAREKEY. ה-ioctls האלה תואמים ל-methods ב-struct blk_crypto_ll_ops. מנהל ההתקן של האחסון מטמיע את השיטות האלה ומתקשר עם חומרת עטיפת המפתחות כדי לבצע את הפעולה המבוקשת. מידע נוסף על ioctl זמין במסמכי התיעוד של ליבת Linux.

ה-ioctls האלה נוספו ב-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 מניח שהחומרה מיישמת 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 ב-ro.crypto.hw_wrapped_keys.kdf לשם ה-KDF כפי שמוגדר ב קוד המקור של הבדיקה.PRODUCT_VENDOR_PROPERTIES כתוצאה מכך, vts_kernel_encryption_test יחפש את KDF במקום kdf1. לדוגמה, כדי לבחור את האפשרות kdf2, משתמשים בפקודה:

PRODUCT_VENDOR_PROPERTIES += ro.crypto.hw_wrapped_keys.kdf=kdf2

במכשירים שמשתמשים ב-KDF שהבדיקה לא תומכת בו, צריך גם להוסיף הטמעה של ה-KDF הזה לבדיקה ולתת לו שם ייחודי.

הפעלת מקשים עטופים

כשתמיכת המכשיר במפתח שעטוף בחומרה פועלת בצורה תקינה, מבצעים את השינויים הבאים בקובץ fstab של המכשיר כדי שמערכת Android תשתמש בו להצפנת FBE ומטא-נתונים:

  • FBE: מוסיפים את הדגל wrappedkey (או wrappedkey_v0 לגרסה הקודמת) לפרמטר fileencryption. לדוגמה, משתמשים ב-fileencryption=::inlinecrypt_optimized+wrappedkey. פרטים נוספים זמינים במאמרי העזרה בנושא FBE.
  • הצפנת מטא-נתונים: מוסיפים את הדגל 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 לצורך תאימות לאחור.