التشفير

يوضّح هذا المستند الطريقة الصحيحة لاستخدام أدوات التشفير في 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) على هذا المتّجه من المفتاح، إذا تم إنشاؤه بشكل مناسب، أو من متّجه إعداد تم تمريره بشكل صريح. إذا مرّرت مفتاح تشفير مستند إلى كلمة مرور لا يحتوي على متّجه إعداد ولم تمرّر متّجه إعداد صريحًا، ستفترض حاليًا برامج التشفير المستندة إلى كلمة مرور على Android أنّ متّجه الإعداد هو صفر.

عند استخدام رموز 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 (المستوى 28 من واجهة برمجة التطبيقات)، تمت إزالة موفّر بنية تشفير لغة البرمجة Java (JCA) الخاصة بالتشفير. إذا كان تطبيقك يطلب مثيلاً من موفّر التشفير، مثلاً من خلال استدعاء الطريقة التالية، سيحدث NoSuchProviderException.

Kotlin

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

Java

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

مكتبة Jetpack security-crypto

تم إيقاف جميع واجهات برمجة التطبيقات في مكتبة security-crypto Jetpack نهائيًا في الإصدار الثابت من الإصدار 1.1.0. لن يتم إصدار أي إصدارات لاحقة من هذه المكتبة.

تظهر التعليقات التوضيحية للإيقاف النهائي إذا كان لديك أي من التبعيات التالية في ملف build.gradle الخاص بوحدة تطبيقك:

Groovy

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: