קריפטוגרפיה

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

ביצוע פעולות קריפטוגרפיות נפוצות

בקטעים הבאים מופיעים קטעי קוד שממחישים איך לבצע פעולות קריפטוגרפיות נפוצות באפליקציה.

הצפנת הודעה

KotlinJava
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
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();

יצירת סיכום הודעות

KotlinJava
val message: ByteArray = ...
val md = MessageDigest.getInstance("SHA-256")
val digest: ByteArray = md.digest(message)
byte[] message = ...;
MessageDigest md = MessageDigest.getInstance("SHA-256");
byte[] digest = md.digest(message);

יצירת חתימה דיגיטלית

צריך אובייקט PrivateKey שמכיל את מפתח החתימה. אפשר ליצור אותו בסביבת זמן הריצה, לקרוא אותו מקובץ שמצורף לאפליקציה או לקבל אותו ממקור אחר, בהתאם לצרכים שלכם.

KotlinJava
val message: ByteArray = ...
val key: PrivateKey = ...
val s = Signature.getInstance("SHA256withECDSA")
       
.apply {
            initSign
(key)
            update
(message)
       
}
val signature: ByteArray = s.sign()
byte[] message = ...;
PrivateKey key = ...;
Signature s = Signature.getInstance("SHA256withECDSA");
s
.initSign(key);
s
.update(message);
byte[] signature = s.sign();

אימות חתימה דיגיטלית

צריך אובייקט PublicKey שמכיל את המפתח הציבורי של החתום. אפשר לקרוא אותו מקובץ שמצורף לאפליקציה, לחלץ אותו מאישור או לקבל אותו ממקור אחר, בהתאם לצרכים שלכם.

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

KotlinJava
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))
       
}
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, כפי שמוצג בדוגמה הבאה:

KotlinJava
Cipher.getInstance("AES/CBC/PKCS7PADDING", "BC")
// OR
Cipher.getInstance("AES/CBC/PKCS7PADDING", Security.getProvider("BC"))
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 מפורש, כפי שמתואר בקטע הקוד הבא:

KotlinJava
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))
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.

KotlinJava
SecureRandom.getInstance("SHA1PRNG", "Crypto")
SecureRandom.getInstance("SHA1PRNG", "Crypto");

ספריית הצפנה לאבטחה של Jetpack

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

GroovyKotlin
dependencies {
    implementation
"androidx.security:security-crypto:1.0.0"
}
dependencies {
    implementation
("androidx.security:security-crypto:1.0.0")
}

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

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