מערכת 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) ואילך, בודקים את הערך המוחזר של
רכיב מאובטח StrongBox KeyMint
במכשירים עם Android מגרסה 9 ואילך (API ברמה 28 ואילך) יכול להיות StrongBox KeyMint, הטמעה של HAL של KeyMint שמגובת על ידי StrongBox. מודול אבטחה לחומרה (HSM) מתייחס באופן כללי לפתרונות מאובטחים לאחסון מפתחות שמתנגדים לפריצות לליבת Linux. לעומת זאת, StrongBox מתייחס באופן ספציפי להטמעות ב-SE מוטמע או במרחבים מאובטחים משולבים (iSE), שמספקים בידוד ועמידות גבוהים יותר בפני פגיעה בהשוואה ל-TEE.
הטמעה של StrongBox KeyMint חייבת לכלול את הרכיבים הבאים:
- מעבד משלו
- אחסון מאובטח
- מחולל מספרים אקראיים אמיתי
- מנגנונים נוספים למניעת פגיעה בחבילות והעלאה לא מורשית של אפליקציות
- טיימר מאובטח
- סיכה להודעה על הפעלה מחדש (או מקבילה), כמו קלט/פלט למטרות כלליות (GPIO)
יש תמיכה בקבוצת משנה של אלגוריתמים וגדלים של מפתחות כדי להתאים להטמעות של StrongBox עם צריכת אנרגיה נמוכה:
- RSA 2048
- AES 128 ו-256
- ECDSA, ECDH P-256
- HMAC-SHA256 (תומך בגדלי מפתחות שבין 8 בייטים ל-64 בייטים, כולל)
- Triple DES
- הודעות APDU באורך מורחב
ב-StrongBox יש גם תמיכה באימות מפתחות.
שימוש ב-StrongBox KeyMint
אפשר להשתמש ב-FEATURE_STRONGBOX_KEYSTORE
כדי לבדוק אם StrongBox זמין במכשיר. אם StrongBox זמין, אפשר לציין את ההעדפה לשמירת המפתח ב-StrongBox KeyMint על ידי העברת הערך true
לשיטות הבאות:
- יצירת מפתח:
KeyGenParameterSpec.Builder.setIsStrongBoxBacked()
- ייבוא מפתחות:
KeyProtection.Builder.setIsStrongBoxBacked()
אם StrongBox KeyMint לא תומך בגודל המפתח או באלגוריתם שצוינו, המערכת תשליך את השגיאה StrongBoxUnavailableException
. במקרה כזה, צריך ליצור או לייבא את המפתח בלי להפעיל את setIsStrongBoxBacked(true)
.
הרשאות לשימוש במפתחות
כדי למנוע שימוש לא מורשה במפתחות במכשיר 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
:
/* * 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()
/* * 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
. מומלץ להוסיף אימות גם לצמד המפתחות הזה.יוצרים את הודעת ה-ASN.1 של
SecureKeyWrapper
בשרת או במכונה מהימנים.המעטפת מכילה את הסכימה הבאה:
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()
:
/* * 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()
/* * 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()
:
/* * 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() }
/* * 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[])
:
/* * 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) }
/* * 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
השלב הבא
- מומלץ לקרוא את המאמר איחוד הגישה למאגר המפתחות ב-ICS בבלוג של מפתחי Android.