מפתחות עטופים בחומרה

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

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

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

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

עיצוב

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

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

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

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

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

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

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

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

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

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

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

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

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

  • ממשק אחד להפיכת inline_encryption_key לקוד ולתכנותו ישירות ב-keyslot של מנוע הקריפטו המוטמע. כך אפשר להצפין או לפענח את תוכן הקבצים בלי שתוכנה תקבל גישה למפתח הגולמי. בליבות הנפוצות של 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 שעומד בדרישות האבטחה. עם זאת, למטרות בדיקה, צריך להטמיע מחדש את אותו KDF בקוד הבדיקה. נכון לעכשיו, נבדקה והוטמעה פונקציית KDF אחת. אפשר למצוא אותה בקוד המקור של vts_kernel_encryption_test. מומלץ שהחומרה תשתמש ב-KDF הזה, שמשתמש ב-NIST SP 800-108 "KDF במצב מונה" עם AES-256-CMAC כ-PRF. חשוב לזכור: כדי שהמפתחות יהיו תואמים, כל החלקים של האלגוריתם צריכים להיות זהים, כולל הבחירה של ההקשרים והתוויות של KDF לכל מפתח משנה.

עטיפת מפתחות

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

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

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

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

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

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

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

צריך לבצע שינויים בתוכנה

ל-AOSP כבר יש מסגרת בסיסית לתמיכה במפתחות שמוגדרים בחומרה. התמיכה כוללת תמיכה ברכיבי מרחב משתמשים כמו vold, וגם תמיכה בליבה (kernel) של Linux ב-blk-crypto, ב-fscrypt וב-dm-default-key.

עם זאת, יש צורך לבצע כמה שינויים ספציפיים להטמעה.

שינויים ב-KeyMint

צריך לשנות את ההטמעה של KeyMint במכשיר כך שתתמוך ב-TAG_STORAGE_KEY ותטמיע את השיטה convertStorageKeyToEphemeral.

ב-Keymaster, נעשה שימוש ב-exportKey במקום ב-convertStorageKeyToEphemeral.

שינויים בליבה של Linux

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

בליבות android14 ואילך, מגדירים את 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.

בדיקה

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

atest -v vts_kernel_encryption_test

קוראים את יומן הבדיקות ומוודאים שלא דילגת על תרחישי הבדיקה של המפתחות שארוזים בחומרה (לדוגמה, FBEPolicyTest.TestAesInlineCryptOptimizedHwWrappedKeyPolicy ו-DmDefaultKeyTest.TestHwWrappedKey) עקב תמיכה בכך שלא זוהו מפתחות עטופים בחומרה, כי במקרה כזה תוצאות הבדיקה עדיין 'מועברות'.

הפעלת מקשים

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

  • FBE: מוסיפים את הדגל wrappedkey_v0 לפרמטר fileencryption. לדוגמה, אפשר להשתמש ב-fileencryption=::inlinecrypt_optimized+wrappedkey_v0. למידע נוסף, עיינו במסמכי העזרה של FBE.
  • הצפנת מטא-נתונים: מוסיפים את הדגל wrappedkey_v0 לפרמטר metadata_encryption. לדוגמה, אפשר להשתמש ב-metadata_encryption=:wrappedkey_v0. מידע נוסף זמין במאמרי העזרה בנושא הצפנת מטא-נתונים.