קריפטוגרפיה

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

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

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

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

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

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

דרגה המלצה
הצפנה ‫AES במצב CBC או GCM עם מפתחות 256-bit (למשל 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

כל ממשקי ה-API בספריית security-crypto Jetpack הוצאו משימוש בגרסה היציבה של גרסה 1.1.0. לא יהיו גרסאות נוספות של הספרייה הזו.

ההערות לגבי הוצאה משימוש מוצגות אם יש לכם תלות באחד מהרכיבים הבאים בקובץ build.gradle של מודול האפליקציה:

גרוב

dependencies {
    implementation "androidx.security:security-crypto:1.1.0"
    // or
    implementation "androidx.security:security-crypto-ktx:1.1.0"
}

Kotlin

dependencies {
    implementation("androidx.security:security-crypto:1.1.0")
    // or
    implementation("androidx.security:security-crypto-ktx:1.1.0")
}

אלגוריתמים נתמכים

אלה מזהי האלגוריתמים של JCA שנתמכים ב-Android: