במסמך הזה מתוארת הדרך הנכונה להשתמש במתקנים הקריפטוגרפיים של Android, ומוצגות בו דוגמאות לשימוש בהם. אם האפליקציה שלכם דורשת אבטחה גבוהה יותר של מפתחות, תוכלו להשתמש במערכת Android Keystore.
ציון ספק רק עם מערכת Android Keystore
אם אתם משתמשים במערכת Android Keystore, חובה לציין ספק.
עם זאת, במצבים אחרים, מערכת Android לא מבטיחה שיהיה ספק מסוים לאלגוריתם נתון. ציון ספק בלי להשתמש במערכת Android Keystore עלול לגרום לבעיות תאימות בגרסאות עתידיות.
בחירת אלגוריתם מומלץ
כשיש לכם אפשרות לבחור באלגוריתם שבו תרצו להשתמש (למשל, אם אתם לא זקוקים לתאימות למערכת של צד שלישי), מומלץ להשתמש באלגוריתמים הבאים:
דרגה | המלצה |
---|---|
הצפנה | AES במצב CBC או GCM עם מפתחות של 256 ביט (כמו AES/GCM/NoPadding ) |
MessageDigest | משפחת SHA-2 (כמו SHA-256 ) |
Mac | HMAC ממשפחת SHA-2 (כמו HMACSHA256 ) |
חתימה | משפחת SHA-2 עם ECDSA (כמו SHA256withECDSA ) |
ביצוע פעולות קריפטוגרפיות נפוצות
בקטעים הבאים מופיעים קטעי קוד שממחישים איך לבצע פעולות קריפטוגרפיות נפוצות באפליקציה.
הצפנת הודעה
Kotlin
val plaintext: ByteArray = ... val keygen = KeyGenerator.getInstance("AES") keygen.init(256) val key: SecretKey = keygen.generateKey() val cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING") cipher.init(Cipher.ENCRYPT_MODE, key) val ciphertext: ByteArray = cipher.doFinal(plaintext) val iv: ByteArray = cipher.iv
Java
byte[] plaintext = ...; KeyGenerator keygen = KeyGenerator.getInstance("AES"); keygen.init(256); SecretKey key = keygen.generateKey(); Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING"); cipher.init(Cipher.ENCRYPT_MODE, key); byte[] ciphertext = cipher.doFinal(plaintext); byte[] iv = cipher.getIV();
יצירת סיכום הודעות
Kotlin
val message: ByteArray = ... val md = MessageDigest.getInstance("SHA-256") val digest: ByteArray = md.digest(message)
Java
byte[] message = ...; MessageDigest md = MessageDigest.getInstance("SHA-256"); byte[] digest = md.digest(message);
יצירת חתימה דיגיטלית
צריך אובייקט PrivateKey
שמכיל את מפתח החתימה. אפשר ליצור אותו בסביבת זמן הריצה, לקרוא אותו מקובץ שמצורף לאפליקציה או לקבל אותו ממקור אחר, בהתאם לצרכים שלכם.
Kotlin
val message: ByteArray = ... val key: PrivateKey = ... val s = Signature.getInstance("SHA256withECDSA") .apply { initSign(key) update(message) } val signature: ByteArray = s.sign()
Java
byte[] message = ...; PrivateKey key = ...; Signature s = Signature.getInstance("SHA256withECDSA"); s.initSign(key); s.update(message); byte[] signature = s.sign();
אימות חתימה דיגיטלית
צריך אובייקט PublicKey
שמכיל את המפתח הציבורי של החתום. אפשר לקרוא אותו מקובץ שמצורף לאפליקציה, לחלץ אותו מאישור או לקבל אותו ממקור אחר, בהתאם לצרכים שלכם.
Kotlin
val message: ByteArray = ... val signature: ByteArray = ... val key: PublicKey = ... val s = Signature.getInstance("SHA256withECDSA") .apply { initVerify(key) update(message) } val valid: Boolean = s.verify(signature)
Java
byte[] message = ...; byte[] signature = ...; PublicKey key = ...; Signature s = Signature.getInstance("SHA256withECDSA"); s.initVerify(key); s.update(message); boolean valid = s.verify(signature);
המורכבות של ההטמעה
יש כמה פרטים בהטמעת הקריפטוגרפיה של Android שנראים לא רגילים, אבל הם נמצאים שם בגלל בעיות תאימות. בקטע הזה נסביר על הבעיות שסביר להניח שתתקלו בהן.
סיכום הודעה של OAEP MGF1
הצפנים של RSA OAEP מוגדרים באמצעות שני סיכומי הודעות שונים: הסיכום 'הראשי' והסיכום MGF1. יש מזהים של Cipher
שכוללים שמות של סיכומים, כמו Cipher.getInstance("RSA/ECB/OAEPwithSHA-256andMGF1Padding")
, שמציין את הסיכום הראשי ולא מציין את הסיכום של MGF1. ב-Android Keystore, נעשה שימוש ב-SHA-1 לדיגסט MGF1, ואילו בספקים קריפטוגרפיים אחרים של Android, שני הדיגסטים זהים.
כדי לקבל יותר שליטה על הדיגטים שבהם האפליקציה משתמשת, מבקשים הצפנה עם OAEPPadding, כמו ב-Cipher.getInstance("RSA/ECB/OAEPPadding")
, ומספקים OAEPParameterSpec
ל-init()
כדי לבחור במפורש את שני הדיגטים.
כך זה נראה בקוד הבא:
Kotlin
val key: Key = ... val cipher = Cipher.getInstance("RSA/ECB/OAEPPadding") .apply { // To use SHA-256 the main digest and SHA-1 as the MGF1 digest init(Cipher.ENCRYPT_MODE, key, OAEPParameterSpec("SHA-256", "MGF1", MGF1ParameterSpec.SHA1, PSource.PSpecified.DEFAULT)) // To use SHA-256 for both digests init(Cipher.ENCRYPT_MODE, key, OAEPParameterSpec("SHA-256", "MGF1", MGF1ParameterSpec.SHA256, PSource.PSpecified.DEFAULT)) }
Java
Key key = ...; Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPPadding"); // To use SHA-256 the main digest and SHA-1 as the MGF1 digest cipher.init(Cipher.ENCRYPT_MODE, key, new OAEPParameterSpec("SHA-256", "MGF1", MGF1ParameterSpec.SHA1, PSource.PSpecified.DEFAULT)); // To use SHA-256 for both digests cipher.init(Cipher.ENCRYPT_MODE, key, new OAEPParameterSpec("SHA-256", "MGF1", MGF1ParameterSpec.SHA256, PSource.PSpecified.DEFAULT));
פונקציונליות שהוצאה משימוש
בקטעים הבאים מתוארת פונקציונליות שהוצאה משימוש. אל תשתמשו בו באפליקציה.
אלגוריתמים של Bouncy Castle
הוצאנו משימוש את ההטמעות של Bouncy Castle של אלגוריתמים רבים. הבעיה הזו רלוונטית רק במקרים שבהם מבקשים במפורש את הספק Bouncy Castle, כפי שמוצג בדוגמה הבאה:
Kotlin
Cipher.getInstance("AES/CBC/PKCS7PADDING", "BC") // OR Cipher.getInstance("AES/CBC/PKCS7PADDING", Security.getProvider("BC"))
Java
Cipher.getInstance("AES/CBC/PKCS7PADDING", "BC"); // OR Cipher.getInstance("AES/CBC/PKCS7PADDING", Security.getProvider("BC"));
כפי שצוין בקטע ציון ספק רק באמצעות מערכת Android Keystore, לא מומלץ לבקש ספק ספציפי. אם פועלים לפי ההנחיה הזו, הוצאת התכונה הזו משימוש לא משפיעה עליכם.
צפנים של הצפנה מבוססת-סיסמה ללא וקטור אתחול
הצפנות מבוססות-סיסמה (PBE) שדורשות וקטור אתחול (IV) יכולות לקבל אותו מהמפתח, אם הוא נוצר בצורה מתאימה, או מ-IV שהוענק באופן מפורש. אם מעבירים מפתח PBE שלא מכיל IV ולא מעבירים IV מפורש, הצפנים של PBE ב-Android מניחים כרגע שה-IV הוא אפס.
כשמשתמשים בהצפנות PBE, תמיד מעבירים IV מפורש, כפי שמתואר בקטע הקוד הבא:
Kotlin
val key: SecretKey = ... val cipher = Cipher.getInstance("PBEWITHSHA256AND256BITAES-CBC-BC") val iv = ByteArray(16) SecureRandom().nextBytes(iv) cipher.init(Cipher.ENCRYPT_MODE, key, IvParameterSpec(iv))
Java
SecretKey key = ...; Cipher cipher = Cipher.getInstance("PBEWITHSHA256AND256BITAES-CBC-BC"); byte[] iv = new byte[16]; new SecureRandom().nextBytes(iv); cipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(iv));
ספק קריפטו
החל מ-Android 9 (רמת API 28), הספק של Crypto Java Cryptography Architecture (JCA) הוסר. אם האפליקציה מבקשת מופע של ספק הקריפטו, למשל באמצעות קריאה לשיטה הבאה, מתרחשת קריאה ל-NoSuchProviderException
.
Kotlin
SecureRandom.getInstance("SHA1PRNG", "Crypto")
Java
SecureRandom.getInstance("SHA1PRNG", "Crypto");
ספריית הצפנה לאבטחה של Jetpack
ספריית הקריפטוגרפיה של Jetpack לא בשימוש. השינוי הזה משפיע רק על מקרים שבהם יש לכם את יחסי התלות הבאים בקובץ build.gradle
של מודול האפליקציה:
Groovy
dependencies { implementation "androidx.security:security-crypto:1.0.0" }
Kotlin
dependencies { implementation("androidx.security:security-crypto:1.0.0") }
אלגוריתמים נתמכים
אלה מזהי האלגוריתם של JCA שנתמכים ב-Android:
AlgorithmParameterGenerator
AlgorithmParameters
CertPathBuilder
CertPathValidator
CertStore
CertificateFactory
Cipher
KeyAgreement
KeyFactory
KeyGenerator
KeyManagerFactory
KeyPairGenerator
KeyStore
Mac
MessageDigest
SSLContext
SSLEngine.Supported
SSLSocket.Supported
SecretKeyFactory
SecureRandom
Signature
TrustManagerFactory