מערכת Android Keystore מאפשרת לאחסן מפתחות קריפטוגרפיים בקונטיינר כדי שיהיה קשה יותר לחלץ אותם מהמכשיר. אחרי שהמפתחות נמצאים במאגר המפתחות, אפשר להשתמש בהם לפעולות קריפטוגרפיות, וחומר המפתחות לא יהיה ניתן לייצוא. בנוסף, מערכת מאגר המפתחות מאפשרת להגביל את הזמנים ואת האופן שבהם אפשר להשתמש במפתחות, למשל לדרוש אימות משתמש לשימוש במפתחות או להגביל את השימוש במפתחות רק במצבים קריפטוגרפיים מסוימים. מידע נוסף זמין בקטע תכונות אבטחה.
מערכת מאגר המפתחות משמשת את ה-API KeyChain
, שהוצג ב-Android 4.0 (רמת API 14), וגם את התכונה של ספק Android Keystore, שהוצגה ב-Android 4.3 (רמת API 18). במסמך הזה מוסבר מתי ואיך משתמשים במערכת Android Keystore.
תכונות אבטחה
מערכת Android Keystore מגינה על חומר המפתח מפני שימוש לא מורשה בשתי דרכים. ראשית, היא מפחיתה את הסיכון לשימוש לא מורשה בחומר המפתח מחוץ למכשיר Android, על ידי מניעת החילוץ של חומר המפתח מתהליכי האפליקציה וממכשיר Android בכללותו. שנית, מערכת מאגר המפתחות מפחיתה את הסיכון לשימוש לא מורשה בחומר המפתח בתוך מכשיר Android, על ידי כך שהאפליקציות צריכות לציין את השימושים המורשים במפתחות שלהן, ולאחר מכן לאכוף את ההגבלות האלה מחוץ לתהליכים של האפליקציות.
מניעת חילוץ
חומר המפתחות של מפתחות Android Keystore מוגן מפני חילוץ באמצעות שני אמצעי אבטחה:
- חומר המפתחות אף פעם לא נכנס לתהליך הבקשה. כשאפליקציה מבצעת פעולות קריפטוגרפיות באמצעות מפתח של Android Keystore, מאחורי הקלעים טקסט ללא הצפנה, טקסט מוצפן והודעות שצריך לחתום עליהן או לאמת אותן מועברים לתהליך מערכת שמבצע את הפעולות הקריפטוגרפיות. אם התהליך של האפליקציה נפרץ, יכול להיות שהתוקף יוכל להשתמש במפתחות של האפליקציה אבל לא יוכל לחלץ את חומר המפתח שלו (לדוגמה, לשימוש מחוץ למכשיר Android).
- אפשר לקשר את חומר המפתח לחומרה המאובטחת של מכשיר Android, כמו סביבת מחשוב אמינה (TEE) או רכיב מאובטח (SE). כשהתכונה הזו מופעלת למפתח, חומר המפתח שלו אף פעם לא נחשף מחוץ לחומרה מאובטחת. אם מערכת ההפעלה של Android נפרצה או שפורץ יכול לקרוא את האחסון הפנימי של המכשיר, יכול להיות שהוא יוכל להשתמש במפתחות של כל אפליקציה ב-Android Keystore במכשיר Android, אבל הוא לא יוכל לחלץ אותם מהמכשיר. התכונה הזו מופעלת רק אם החומרה המאובטחת של המכשיר תומכת בשילוב המסוים של אלגוריתם המפתחות, מצבי החסימה, סכימות המרווח הפנימי ותקצירים שבהם מותר להשתמש במפתח.
כדי לבדוק אם התכונה מופעלת במפתח, צריך לקבל את
KeyInfo
למפתח. השלב הבא תלוי בגרסת ה-SDK של היעד של האפליקציה:- אם האפליקציה שלכם מטרגטת ל-Android 10 (רמת API 29) ואילך, בודקים את הערך המוחזר של
getSecurityLevel()
. ערכים שמוחזרים ותואמים ל-KeyProperties.SecurityLevelEnum.TRUSTED_ENVIRONMENT
או ל-KeyProperties.SecurityLevelEnum.STRONGBOX
מציינים שהמפתח נמצא בחומרה מאובטחת. - אם האפליקציה שלכם מטרגטת ל-Android 9 (רמת API 28) וגרסאות ישנות יותר, בודקים את הערך הבווליאני המוחזר של
KeyInfo.isInsideSecurityHardware()
.
- אם האפליקציה שלכם מטרגטת ל-Android 10 (רמת API 29) ואילך, בודקים את הערך המוחזר של
מודול אבטחה לחומרה
במכשירים נתמכים עם Android 9 (רמת API 28) ואילך יכול להיות StrongBox Keymaster, הטמעה של HAL של Keymaster או Keymint שנמצאת ברכיב מאובטח כמו מודול אבטחה לחומרה. מודולים של אבטחת חומרה יכולים להתייחס להטמעות שונות של אחסון מפתחות, שבהן פריצה בליבה (kernel) של Linux לא יכולה לחשוף אותם, למשל TEE, מערכת StrongBox מתייחסת באופן מפורש למכשירים כמו אלמנטים כמו אלמנטים מאובטחים מוטמעים (eSE) או יחידות עיבוד מאובטחות ב-SoC (iSE).
המודול מכיל את הפריטים הבאים:
- מעבד משלו
- אחסון מאובטח
- מחולל מספרים אקראיים אמיתי
- מנגנונים נוספים למניעת פגיעה בחבילות והעלאה לא מורשית של אפליקציות
- טיימר מאובטח
- סיכה להודעה על הפעלה מחדש (או מקבילה), כמו קלט/פלט למטרות כלליות (GPIO)
כדי לתמוך בהטמעות של StrongBox עם צריכת אנרגיה נמוכה, יש תמיכה בקבוצת משנה של אלגוריתמים וגדלים של מפתחות:
- RSA 2048
- AES 128 ו-256
- ECDSA, ECDH P-256
- HMAC-SHA256 (תומך בגדלי מפתחות בין 8 בייטים ל-64 בייטים, כולל)
- Triple DES
- APDU באורך מורחב
- אימות עם מפְתח
- תמיכה בתיקון H לשדרוג
כשיוצרים או מייבאים מפתחות באמצעות הכיתה KeyStore
, מציינים את ההעדפה לשמירת המפתח ב-StrongBox Keymaster על ידי העברת true
לשיטה setIsStrongBoxBacked()
.
אמנם StrongBox איטי יותר ומוגבל במשאבים (כלומר הוא תומך בפחות פעולות בו-זמנית) בהשוואה ל-TEE, אבל הוא מספק ערבויות אבטחה טובות יותר מפני מתקפות פיזיות ומתקפות בערוץ צדדי. אם אתם רוצים לתת עדיפות להבטחות אבטחה גבוהות יותר על פני יעילות המשאבים של האפליקציה, מומלץ להשתמש ב-StrongBox במכשירים שבהם הוא זמין. בכל מקום שבו StrongBox לא זמין, האפליקציה תמיד יכולה לחזור לסביבת TEE ולאחסן את חומרי המפתחות.
הרשאות לשימוש במפתחות
כדי למנוע שימוש לא מורשה במפתחות במכשיר Android, Android Keystore מאפשר לאפליקציות לציין את השימושים המורשים במפתחות שלהן כשהן יוצרות או מייבאות את המפתחות. אחרי שיוצרים או מייבאים מפתח, אי אפשר לשנות את ההרשאות שלו. לאחר מכן, הרשאות נאכפות על ידי Android Keystore בכל פעם שמשתמשים במפתח. זוהי תכונת אבטחה מתקדמת ששימושית בדרך כלל רק אם הדרישות שלכם הן שפגיעה בתהליך הבקשה אחרי יצירת המפתח או ייבואו (אבל לא לפני או במהלכו) לא תוביל לשימוש לא מורשה במפתח.
ההרשאות הנתמכות לשימוש במפתחות מחולקות לקטגוריות הבאות:
- קריפטוגרפיה: אפשר להשתמש במפתח רק עם אלגוריתמים, פעולות או מטרות מורשים של מפתחות (הצפנה, פענוח, חתימה, אימות), סכמות של מילוי, מצבי בלוק או סיכומי גיבוב.
- מרווח זמן לתקפות זמנית: המפתח מורשה לשימוש רק במהלך מרווח זמן מוגדר.
- אימות משתמש: אפשר להשתמש במפתח רק אם המשתמש אומת לאחרונה. דרישה לאימות משתמש לשימוש במפתח
כמדד אבטחה נוסף למפתחות שהחומר שלהם נמצא בחומרה מאובטחת (ראו KeyInfo.isInsideSecurityHardware()
או, לאפליקציות שמטרגטות את Android 10 (רמת API 29) ואילך, KeyInfo.getSecurityLevel()
), יכול להיות שהחומרה המאובטחת תאכוף הרשאות מסוימות לשימוש במפתח, בהתאם למכשיר Android.
בדרך כלל, חומרה מאובטחת אוכפת הרשאות קריפטוגרפיות והרשאות אימות משתמשים. עם זאת, בדרך כלל לא מתבצעת באמצעי חומרה מאובטחים אכיפה של הרשאות זמניות עם מרווח תוקף, כי בדרך כלל אין להם שעון עצמאי ומאובטח בזמן אמת.
תוכלו להריץ שאילתה אם החומרה המאובטחת אוכפת את הרשאת אימות המשתמש של המפתח באמצעות KeyInfo.isUserAuthenticationRequirementEnforcedBySecureHardware()
.
בחירה בין מפתחות לבין הספק של Android Keystore
משתמשים ב-API KeyChain
כשרוצים פרטי כניסה ברמת המערכת. כשאפליקציה מבקשת להשתמש בפרטי כניסה כלשהם דרך ה-API של KeyChain
, המשתמשים יכולים לבחור, דרך ממשק משתמש שסופק על ידי המערכת, לאילו פרטי כניסה מותקנים האפליקציה תהיה גישה. כך כמה אפליקציות יכולות להשתמש באותה קבוצת פרטי כניסה עם הסכמה מהמשתמשים.
שימוש בספק Android Keystore כדי לאפשר לאפליקציה מסוימת לאחסן את פרטי הכניסה שלה, שרק היא יכולה לגשת אליהם.
כך אפליקציות יכולות לנהל פרטי כניסה שרק הן יכולות להשתמש בהם, תוך שמירה על אותם יתרונות אבטחה ש-KeyChain
API מספק לפרטי כניסה ברמת המערכת.
בשיטה הזו המשתמש לא צריך לבחור את פרטי הכניסה.
שימוש בספק Android Keystore
כדי להשתמש בתכונה הזו, משתמשים במחלקות הסטנדרטיות KeyStore
ו-KeyPairGenerator
או KeyGenerator
, יחד עם הספק AndroidKeyStore
שהוצג ב-Android 4.3 (רמת API 18).
AndroidKeyStore
רשום כסוג KeyStore
לשימוש עם השיטה KeyStore.getInstance(type)
וכספק לשימוש בשיטות KeyPairGenerator.getInstance(algorithm, provider)
ו-KeyGenerator.getInstance(algorithm, provider)
.
פעולות קריפטוגרפיות עשויות להיות זמן רב, לכן מומלץ לא להשתמש ב-AndroidKeyStore
ב-thread הראשי של האפליקציה כדי לוודא שממשק המשתמש של האפליקציה ימשיך להגיב. (StrictMode
יכול לעזור לכם למצוא מקומות שבהם זה לא המצב).
יצירת מפתח פרטי או סודי חדש
כדי ליצור KeyPair
חדש שמכיל PrivateKey
, צריך לציין את המאפיינים הראשוניים של אישור X.509. אפשר להשתמש ב-KeyStore.setKeyEntry()
כדי להחליף את האישור בשלב מאוחר יותר באישור חתום על ידי רשות אישורים (CA).
כדי ליצור את זוג המפתחות, משתמשים ב-KeyPairGenerator
עם KeyGenParameterSpec
:
Kotlin
/* * Generate a new EC key pair entry in the Android Keystore by * using the KeyPairGenerator API. The private key can only be * used for signing or verification and only with SHA-256 or * SHA-512 as the message digest. */ val kpg: KeyPairGenerator = KeyPairGenerator.getInstance( KeyProperties.KEY_ALGORITHM_EC, "AndroidKeyStore" ) val parameterSpec: KeyGenParameterSpec = KeyGenParameterSpec.Builder( alias, KeyProperties.PURPOSE_SIGN or KeyProperties.PURPOSE_VERIFY ).run { setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA512) build() } kpg.initialize(parameterSpec) val kp = kpg.generateKeyPair()
Java
/* * Generate a new EC key pair entry in the Android Keystore by * using the KeyPairGenerator API. The private key can only be * used for signing or verification and only with SHA-256 or * SHA-512 as the message digest. */ KeyPairGenerator kpg = KeyPairGenerator.getInstance( KeyProperties.KEY_ALGORITHM_EC, "AndroidKeyStore"); kpg.initialize(new KeyGenParameterSpec.Builder( alias, KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY) .setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA512) .build()); KeyPair kp = kpg.generateKeyPair();
ייבוא מפתחות מוצפנים לחומרה מאובטחת
ב-Android 9 (API ברמה 28) ואילך אפשר לייבא מפתחות מוצפנים באופן מאובטח למאגר המפתחות באמצעות פורמט מפתחות עם קידוד ASN.1. לאחר מכן, Keymaster מפענח את המפתחות במאגר המפתחות, כך שתוכן המפתחות אף פעם לא מופיע כטקסט ללא הצפנה בזיכרון המארח של המכשיר. התהליך הזה מספק אבטחה נוספת לפענוח המפתחות.
כדי לתמוך בייבוא מאובטח של מפתחות מוצפנים למאגר המפתחות, צריך לבצע את השלבים הבאים:
יוצרים זוג מפתחות שמשתמש במטרה
PURPOSE_WRAP_KEY
. מומלץ להוסיף גם אימות (attestation) לזוג המפתחות הזה.יוצרים את הודעת ה-ASN.1 של
SecureKeyWrapper
בשרת או במכונה מהימנים.ה-wrapper מכיל את הסכימה הבאה:
KeyDescription ::= SEQUENCE { keyFormat INTEGER, authorizationList AuthorizationList } SecureKeyWrapper ::= SEQUENCE { wrapperFormatVersion INTEGER, encryptedTransportKey OCTET_STRING, initializationVector OCTET_STRING, keyDescription KeyDescription, secureKey OCTET_STRING, tag OCTET_STRING }
יוצרים אובייקט
WrappedKeyEntry
ומעבירים את הודעת ASN.1 כמערך בייטים.מעבירים את האובייקט
WrappedKeyEntry
לטעינה יתר שלsetEntry()
שמקבלת אובייקטKeystore.Entry
.
עבודה עם רשומות במאגר המפתחות
אפשר לגשת לספק AndroidKeyStore
דרך כל ממשקי ה-API הרגילים של KeyStore
.
הצגת רשימה של רשומות
כדי לקבל רשימה של הרשומות במאגר המפתחות, קוראים ל-method aliases()
:
Kotlin
/* * Load the Android KeyStore instance using the * AndroidKeyStore provider to list the currently stored entries. */ val ks: KeyStore = KeyStore.getInstance("AndroidKeyStore").apply { load(null) } val aliases: Enumeration<String> = ks.aliases()
Java
/* * Load the Android KeyStore instance using the * AndroidKeyStore provider to list the currently stored entries. */ KeyStore ks = KeyStore.getInstance("AndroidKeyStore"); ks.load(null); Enumeration<String> aliases = ks.aliases();
חתימה ואימות של נתונים
כדי לחתום על נתונים, מאחזרים את KeyStore.Entry
ממאגר המפתחות ומשתמשים בממשקי ה-API של Signature
, כמו sign()
:
Kotlin
/* * Use a PrivateKey in the KeyStore to create a signature over * some data. */ val ks: KeyStore = KeyStore.getInstance("AndroidKeyStore").apply { load(null) } val entry: KeyStore.Entry = ks.getEntry(alias, null) if (entry !is KeyStore.PrivateKeyEntry) { Log.w(TAG, "Not an instance of a PrivateKeyEntry") return null } val signature: ByteArray = Signature.getInstance("SHA256withECDSA").run { initSign(entry.privateKey) update(data) sign() }
Java
/* * Use a PrivateKey in the KeyStore to create a signature over * some data. */ KeyStore ks = KeyStore.getInstance("AndroidKeyStore"); ks.load(null); KeyStore.Entry entry = ks.getEntry(alias, null); if (!(entry instanceof PrivateKeyEntry)) { Log.w(TAG, "Not an instance of a PrivateKeyEntry"); return null; } Signature s = Signature.getInstance("SHA256withECDSA"); s.initSign(((PrivateKeyEntry) entry).getPrivateKey()); s.update(data); byte[] signature = s.sign();
באופן דומה, מאמתים נתונים באמצעות השיטה verify(byte[])
:
Kotlin
/* * Verify a signature previously made by a private key in the * KeyStore. This uses the X.509 certificate attached to the * private key in the KeyStore to validate a previously * generated signature. */ val ks = KeyStore.getInstance("AndroidKeyStore").apply { load(null) } val entry = ks.getEntry(alias, null) as? KeyStore.PrivateKeyEntry if (entry == null) { Log.w(TAG, "Not an instance of a PrivateKeyEntry") return false } val valid: Boolean = Signature.getInstance("SHA256withECDSA").run { initVerify(entry.certificate) update(data) verify(signature) }
Java
/* * Verify a signature previously made by a private key in the * KeyStore. This uses the X.509 certificate attached to the * private key in the KeyStore to validate a previously * generated signature. */ KeyStore ks = KeyStore.getInstance("AndroidKeyStore"); ks.load(null); KeyStore.Entry entry = ks.getEntry(alias, null); if (!(entry instanceof PrivateKeyEntry)) { Log.w(TAG, "Not an instance of a PrivateKeyEntry"); return false; } Signature s = Signature.getInstance("SHA256withECDSA"); s.initVerify(((PrivateKeyEntry) entry).getCertificate()); s.update(data); boolean valid = s.verify(signature);
דרישה לאימות משתמש לשימוש במפתח
כשיוצרים או מייבאים מפתח ל-AndroidKeyStore
, אפשר לציין שהשימוש במפתח מורשה רק אם המשתמש אומת. המשתמש מאומת באמצעות קבוצת משנה של פרטי הכניסה המאובטחים שלו לנעילה המסך (קו ביטול נעילה/קוד אימות/סיסמה, פרטי כניסה ביומטריים).
זוהי תכונת אבטחה מתקדמת שמועילה בדרך כלל רק אם הדרישות שלכם הן שפגיעה בתהליך האפליקציה לאחר יצירה/ייבוא של מפתחות (אבל לא לפני או במהלכה) לא יכולה לעקוף את הדרישה לאימות המשתמש כדי להשתמש במפתח.
כשמגדירים מפתח לשימוש רק אם המשתמש אומת, אפשר להפעיל אותו באחת מהשיטות הבאות:setUserAuthenticationParameters()
- מתן הרשאה למשך פרק זמן מסוים
- כל המפתחות מורשים לשימוש ברגע שהמשתמש מאמת באמצעות אחד מהפרטים המזהים שצוינו.
- מתן הרשאה למשך פעולה קריפטוגרפית ספציפית
כל פעולה שכוללת מפתח ספציפי חייבת לקבל אישור ספציפי מהמשתמש.
האפליקציה מתחילה את התהליך הזה באמצעות קריאה ל-
authenticate()
במכונה שלBiometricPrompt
.
בכל מפתח שיוצרים אפשר לבחור לתמוך בפרטי כניסה ביומטריים חזקים,
בפרטי כניסה במסך הנעילה או בשני סוגי פרטי הכניסה. כדי לבדוק אם המשתמש הגדיר את פרטי הכניסה שעליהם המפתח של האפליקציה מסתמך, צריך להפעיל את הפונקציה canAuthenticate()
.
אם מפתח תומך רק בפרטי כניסה ביומטריים, הוא מאבד את התוקף כברירת מחדל בכל פעם שמתווספים הרשמות ביומטריות חדשות. אפשר להגדיר שהמפתח יישאר בתוקף כשמוסיפים הרשמות ביומטריות חדשות. כדי לעשות זאת, מעבירים את false
אל setInvalidatedByBiometricEnrollment()
.
מידע נוסף על הוספת יכולות אימות ביומטרי לאפליקציה, כולל הצגת תיבת דו-שיח של אימות ביומטרי
אלגוריתמים נתמכים
Cipher
KeyGenerator
KeyFactory
KeyStore
(תומך באותם סוגי מפתחות כמוKeyGenerator
ו-KeyPairGenerator
)KeyPairGenerator
Mac
Signature
SecretKeyFactory
מאמרים בבלוגים
תוכלו לקרוא את המאמר איחוד גישה ל-Key Store ב-ICS.