קריפטוגרפיה

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

ציון ספק רק באמצעות מערכת Android Keystore

אם אתם משתמשים במערכת Android Keystore, חובה לציין ספק.

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

בחירת אלגוריתם מומלץ

כשיש לכם אפשרות לבחור באלגוריתם שבו תרצו להשתמש (למשל, אם אתם לא זקוקים לתאימות למערכת של צד שלישי), מומלץ להשתמש באלגוריתמים הבאים:

דרגה המלצה
הצפנה AES במצב CBC או GCM עם מפתחות של 256 ביט (כמו AES/GCM/NoPadding)
תקציר ההודעה משפחת 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. ב-Keystore (Keystore) Android, 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, כפי שמוצג בדוגמה הבאה:

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: