קריפטוגרפיה

במסמך הזה מתוארת הדרך הנכונה להשתמש במתקנים הקריפטוגרפיים של 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: