التشفير

يوضّح هذا المستند الطريقة الصحيحة لاستخدام مرافق التشفير في Android ويضمّ بعض الأمثلة على استخدامها. إذا كان تطبيقك يتطلّب مزيدًا من أمان المفتاح، استخدِم نظام "متجر مفاتيح Android".

تحديد مقدّم خدمة باستخدام نظام "متجر مفاتيح Android" فقط

إذا كنت تستخدم نظام "متجر مفاتيح Android"، يجب تحديد مقدّم خدمة.

وفي حالات أخرى، لا يضمن Android استخدام مقدّم خدمة معيّن لخوارزمية معيّنة. قد يؤدي تحديد موفِّر بدون استخدام نظام ملف تخزين مفاتيح Android إلى حدوث مشاكل توافق في الإصدارات المستقبلية.

اختيار خوارزمية مقترَحة

عندما يكون لديك خيار اختيار الخوارزمية التي تريد استخدامها (مثلاً عندما لا تتطلّب التوافق مع نظام تابع لجهة خارجية)، ننصحك باستخدام الخوارزميات التالية:

الفئة مقترَح
التشفير ‫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 غير محدّد. بالنسبة إلى "ملف تخزين مفاتيح Android"، يتم استخدام SHA-1 لخلاصة MGF1، في حين أنّ خلاصتَي MGF1 وSHA-1 متطابقتَين لموفّري خدمات التشفير الآخرين في Android.

لمزيد من التحكّم في الملخّصات التي يستخدمها تطبيقك، اطلب رمزًا مع إضافة OAEPP، كما هو الحال في 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" فقط، لا يُنصح بطلب مقدّم خدمة محدّد. إذا اتّبعت هذه الإرشادات، لن يؤثّر إيقاف هذه الميزة نهائيًا فيك.

رموز التشفير المستندة إلى كلمات المرور بدون متّجه إعداد

إنّ رموز التشفير المستندة إلى كلمة المرور (PBE) التي تتطلّب متّجه إعداد (IV) يمكنها الحصول عليه من المفتاح، إذا تم إنشاؤه بشكل مناسب، أو من متّجه إعداد تم تمريره صراحةً. في حال تم تمرير مفتاح PBE لا يحتوي على مُوجِّه إعداد ولا تتم إرسال قيمة مُوجِّه إعداد صريحة، تفترض رموز التشفير PBE على Android حاليًا أنّ قيمة مُوجِّه الإعداد هي صفر.

عند استخدام رموز التشفير PBE، يجب دائمًا تمرير مفتاح تشفير السلسلة العشوائية الصريح، كما هو موضّح في مقتطف الرمز البرمجي التالي:

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

تم إيقاف مكتبة التشفير في Jetpack Security نهائيًا. لا يؤثر ذلك إلا في الحالات التي يكون فيها لديك التبعيات التالية في ملف build.gradle الخاص بوحدة تطبيقك:

رائع

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

Kotlin

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

الخوارزميات المتوافقة

في ما يلي معرّفات خوارزمية JCA المتوافقة مع Android: