نظام ملف تخزين مفاتيح Android

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

يستخدم نظام تخزين المفاتيح واجهة برمجة التطبيقات KeyChain التي تم طرحها في Android 4.0 (المستوى 14 لواجهة برمجة التطبيقات)، بالإضافة إلى ميزة موفِّر "تخزين مفاتيح Android" التي تم طرحها في Android 4.3 (المستوى 18 لواجهة برمجة التطبيقات). يتناول هذا المستند حالات استخدام نظام "متجر مفاتيح Android" وكيفية استخدامه.

ميزات الأمان

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

منع الاستخراج

تتم حماية مادة مفاتيح "متجر مفاتيح Android" من الاستخراج باستخدام تدبيرَين للأمان:

  • لا يتم إدخال المادة الرئيسية مطلقًا في عملية تقديم الطلب. عندما يُجري تطبيق عمليات التشفير باستخدام مفتاح "متجر مفاتيح Android"، يتمّ من وراء الكواليس إرسال النص العادي والنص المشفَّر والرسائل التي يجب توقيعها أو التحقّق منها إلى عملية نظام تنفِّذ العمليات التشفيرية. في حال اختراق عملية التطبيق، قد يتمكّن المهاجم من استخدام مفاتيح التطبيق ولكن لا يمكنه استخراج مادة المفتاح (على سبيل المثال، لاستخدامها خارج جهاز Android).
  • يمكن ربط مادة المفتاح بالأجهزة الآمنة في جهاز Android، مثل بيئة التنفيذ الموثوقة (TEE) أو العنصر الآمن (SE). عند تفعيل هذه الميزة لمفتاح، لا يتم أبدًا عرض مادة مفتاح الصعوبة خارج الأجهزة الآمنة. إذا تم اختراق نظام التشغيل Android أو تمكّن مهاجم من قراءة مساحة التخزين الداخلية للجهاز، قد يتمكّن المهاجم من استخدام مفاتيح "متجر مفاتيح Android" لأي تطبيق على جهاز Android، ولكن لا يمكنه استخراجها من الجهاز. لا يتم تفعيل هذه الميزة إلا إذا كان الجهاز الآمن يتيح استخدام مجموعة معيّنة من خوارزمية المفتاح وأوضاع التشفير ومخططات الحشو والملخّصات التي يُسمح باستخدام المفتاح معها.

    للتحقّق مما إذا كانت الميزة مفعّلة لمفتاح معيّن، يمكنك الحصول على KeyInfo للمفتاح. تعتمد الخطوة التالية على إصدار حزمة SDK المستهدَف لتطبيقك:

    • إذا كان تطبيقك يستهدف الإصدار 10 من نظام التشغيل Android (المستوى 29 من واجهة برمجة التطبيقات) أو إصدارًا أحدث، تحقَّق من قيمةgetSecurityLevel() المعروضة. تشير القيم المعروضة التي تتطابق مع KeyProperties.SecurityLevelEnum.TRUSTED_ENVIRONMENT أو KeyProperties.SecurityLevelEnum.STRONGBOX إلى أنّ المفتاح مخزّن في جهاز آمن.
    • إذا كان تطبيقك يستهدف Android 9 (المستوى 28 من واجهة برمجة التطبيقات) أو إصدارًا أقدم، تحقّق من قيمة الإرجاع المَنطقي KeyInfo.isInsideSecurityHardware().

عنصر الأمان StrongBox KeyMint

يمكن أن تتضمّن الأجهزة التي تعمل بالإصدار 9 من نظام التشغيل Android (المستوى 28 من واجهة برمجة التطبيقات) أو إصدار أحدث StrongBox KeyMint، وهو StrongBox ينفِّذ HAL KeyMint. في حين أنّ وحدات أمان الأجهزة (HSM) تشير بشكل عام إلى حلول تخزين مفاتيح آمنة مقاومة للاختراقات في ملف معالجة طلبات Linux، يشير StrongBox تحديدًا إلى عمليات التنفيذ في وحدات إشعال المدمجة أو وحدات إشعال آمنة مدمجة (iSE)، ما يوفر عزلًا ومقاومة للعبث أكبر مقارنةً ببيئة التنفيذ الموثوقة.

يجب أن يتضمّن تطبيق StrongBox KeyMint ما يلي:

  • وحدة المعالجة المركزية الخاصة به
  • مساحة تخزين آمنة
  • أداة إنشاء أرقام عشوائية حقيقية
  • آليات إضافية لمنع التلاعب بالحِزم وتحميل التطبيقات بدون إذن
  • موقّت آمن
  • دبوس إشعار إعادة التشغيل (أو ما يعادله)، مثل الإدخال/الإخراج العام (GPIO)

تتوفّر مجموعة فرعية من الخوارزميات وأحجام المفاتيح لاستيعاب عمليات تنفيذ StrongBox ذات الطاقة المنخفضة:

  • RSA 2048
  • ‫AES 128 و256
  • ECDSA وECDH P-256
  • ‫HMAC-SHA256 (تتيح أحجام مفاتيح التشفير بين 8 و64 بايت)
  • ‫Triple DES
  • وحدات APDU ذات الطول الموسّع

يتيح StrongBox أيضًا مصادقة المفتاح.

استخدام StrongBox KeyMint

استخدِم FEATURE_STRONGBOX_KEYSTORE للتحقّق مما إذا كانت ميزة StrongBox متوفّرة على الجهاز. إذا كان تطبيق StrongBox متاحًا، يمكنك تحديد الخيار المفضّل لتخزين المفتاح في StrongBox KeyMint من خلال تمرير true إلى methods التالية:

إذا لم يكن StrongBox KeyMint متوافقًا مع الخوارزمية المحدّدة أو حجم المفتاح، سيُعرِض إطار العمل خطأ StrongBoxUnavailableException. إذا حدث ذلك، يمكنك إنشاء ملف ملف تعريف البريد الإلكتروني أو استيراده بدون الاتصال بـ setIsStrongBoxBacked(true).

تفويضات استخدام المفاتيح

لتجنُّب الاستخدام غير المصرَّح به للمفاتيح على جهاز Android، يتيح "متجر مفاتيح Android" للتطبيقات تحديد الاستخدامات المصرَّح بها لمفاتيحها عند إنشاء المفاتيح أو استيرادها. بعد إنشاء مفتاح أو استيراده، لا يمكن تغيير أذوناته. بعد ذلك، يفرض "مخزن مفاتيح Android" الأذونات كلما تم استخدام المفتاح. هذه ميزة أمان متقدّمة لا تكون مفيدة بشكل عام إلا إذا كانت متطلباتك هي أنّ اختراق عملية تقديم طلبك بعد إنشاء/استيراد المفتاح (ولكن ليس قبل أو أثناء ذلك) لا يمكن أن يؤدي إلى استخدامات غير مصرّح بها للمفتاح.

تندرج أذونات استخدام المفاتيح المسموح بها ضمن الفئات التالية:

  • التشفير: لا يمكن استخدام المفتاح إلا مع خوارزميات المفاتيح أو العمليات أو الأغراض المعتمَدة (التشفير أو فك التشفير أو التوقيع أو التحقّق) أو مخططات الحشو أو أوضاع الكتل أو الملخّصات.
  • فترة الصلاحية الزمنية: لا يُسمح باستخدام المفتاح إلا خلال فترة زمنية محدّدة.
  • مصادقة المستخدم: لا يمكن استخدام المفتاح إلا إذا تم مصادقة المستخدم مؤخرًا. راجِع مقالة طلب مصادقة المستخدم لاستخدام المفتاح.

كإجراء أمان إضافي للمفاتيح التي تحتوي على مادة المفتاح داخل جهاز آمن (راجِع KeyInfo.isInsideSecurityHardware() أو، بالنسبة إلى التطبيقات التي تستهدف الإصدار 10 من نظام التشغيل Android (المستوى 29 من واجهة برمجة التطبيقات) أو إصدارًا أحدث، KeyInfo.getSecurityLevel())، قد تفرض الأجهزة الآمنة بعض أذونات استخدام المفتاح، استنادًا إلى جهاز Android. تفرض الأجهزة الآمنة عادةً أذونات التشفير ومصادقة المستخدمين. ومع ذلك، لا تفرض الأجهزة الآمنة عادةً أذونات فاصل الصلاحية الزمني، لأنّها لا تحتوي عادةً على ساعة مستقلة وآمنة في الوقت الفعلي.

يمكنك الاستعلام عمّا إذا كان جهاز الأمان يفرض إذن مصادقة المستخدم للمفتاح باستخدام KeyInfo.isUserAuthenticationRequirementEnforcedBySecureHardware().

الاختيار بين سلسلة مفاتيح وموفِّر ملف تخزين مفاتيح Android

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

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

استخدام موفِّر "مفتاح Android"

لاستخدام هذه الميزة، يمكنك استخدام فئتَي KeyStore وKeyPairGenerator أو KeyGenerator العاديتَين مع موفِّر AndroidKeyStore الذي تم تقديمه في Android 4.3 (المستوى 18 من واجهة برمجة التطبيقات).

تم تسجيل AndroidKeyStore كنوع KeyStore لاستخدامه مع طريقة KeyStore.getInstance(type) وكمقدّم خدمة لاستخدامه مع الطريقتَين KeyPairGenerator.getInstance(algorithm, provider) وKeyGenerator.getInstance(algorithm, provider).

بما أنّ العمليات التشفيرية قد تستغرق وقتًا طويلاً، يجب أن تتجنّب التطبيقات استخدام AndroidKeyStore في سلسلة التعليمات الرئيسية لضمان أن تظل واجهة المستخدم في التطبيق قيد المعالجة. (يمكن أن يساعدك تطبيق StrictMode في العثور على أماكن لا ينطبق فيها ذلك).

إنشاء مفتاح خاص أو سري جديد

لإنشاء KeyPair جديد يحتوي على PrivateKey، يجب تحديد السمات الأولية لشهادة X.509. يمكنك استخدام KeyStore.setKeyEntry() لاستبدال الشهادة في وقت لاحق بشهادة موقَّعة من قِبل هيئة إصدار الشهادات (CA).

لإنشاء مفتاحَي التشفير، استخدِم KeyPairGenerator مع KeyGenParameterSpec:

KotlinJava
/*
 * Generate a new EC key pair entry in the Android Keystore by
 * using the KeyPairGenerator API. The private key can only be
 * used for signing or verification and only with SHA-256 or
 * SHA-512 as the message digest.
 */
val kpg: KeyPairGenerator = KeyPairGenerator.getInstance(
        KeyProperties.KEY_ALGORITHM_EC,
        "AndroidKeyStore"
)
val parameterSpec: KeyGenParameterSpec = KeyGenParameterSpec.Builder(
        alias,
        KeyProperties.PURPOSE_SIGN or KeyProperties.PURPOSE_VERIFY
).run {
    setDigests(KeyProperties.DIGEST_SHA256, KeyProperties.DIGEST_SHA512)
    build()
}

kpg.initialize(parameterSpec)

val kp = kpg.generateKeyPair()
/*
 * Generate a new EC key pair entry in the Android Keystore by
 * using the KeyPairGenerator API. The private key can only be
 * used for signing or verification and only with SHA-256 or
 * SHA-512 as the message digest.
 */
KeyPairGenerator kpg = KeyPairGenerator.getInstance(
        KeyProperties.KEY_ALGORITHM_EC, "AndroidKeyStore");
kpg.initialize(new KeyGenParameterSpec.Builder(
        alias,
        KeyProperties.PURPOSE_SIGN | KeyProperties.PURPOSE_VERIFY)
        .setDigests(KeyProperties.DIGEST_SHA256,
            KeyProperties.DIGEST_SHA512)
        .build());

KeyPair kp = kpg.generateKeyPair();

استيراد مفاتيح مشفَّرة إلى جهاز آمن

يتيح لك الإصدار 9 من Android (المستوى 28 لواجهة برمجة التطبيقات) والإصدارات الأحدث استيراد مفاتيح مشفّرة بأمان إلى ملف تخزين المفاتيح باستخدام تنسيق مفتاح مُشفَّر بترميز ASN.1. بعد ذلك، يفكّ رمز مفاتيح التشفير في "Keymaster" في مخزن المفاتيح، لذا لا يظهر محتوى المفاتيح أبدًا كنص عادي في ذاكرة المضيف للجهاز. توفّر هذه العملية أمانًا إضافيًا لفك تشفير المفاتيح.

لإتاحة استيراد المفاتيح المشفَّرة بأمان إلى ملف تخزين المفاتيح، أكمِل الخطوات التالية:

  1. أنشئ مفتاحَي تشفير يستخدمان الغرض PURPOSE_WRAP_KEY. ننصحك أيضًا بإضافة شهادة اعتماد إلى هذا الزوج من المفاتيح.

  2. على خادم أو جهاز تثق به، أنشئ رسالة ASN.1 لملف العميل SecureKeyWrapper.

    يحتوي الغلاف على المخطّط التالي:

       KeyDescription ::= SEQUENCE {
           keyFormat INTEGER,
           authorizationList AuthorizationList
       }
    
       SecureKeyWrapper ::= SEQUENCE {
           wrapperFormatVersion INTEGER,
           encryptedTransportKey OCTET_STRING,
           initializationVector OCTET_STRING,
           keyDescription KeyDescription,
           secureKey OCTET_STRING,
           tag OCTET_STRING
       }
    
  3. أنشئ عنصرًا من نوع WrappedKeyEntry ، مع إدخال رسالة ASN.1 كصفيف بايت.

  4. نقْل عنصر WrappedKeyEntry هذا إلى طريقة التحميل الزائد لدالة setEntry() التي تقبل عنصر Keystore.Entry.

العمل مع إدخالات ملف تخزين المفاتيح

يمكنك الوصول إلى مقدّم AndroidKeyStore من خلال جميع واجهات برمجة التطبيقات العادية KeyStore.

إدخالات القائمة

يمكنك إدراج الإدخالات في ملف تخزين المفاتيح من خلال استدعاء الطريقة aliases():

KotlinJava
/*
 * Load the Android KeyStore instance using the
 * AndroidKeyStore provider to list the currently stored entries.
 */
val ks: KeyStore = KeyStore.getInstance("AndroidKeyStore").apply {
   load(null)
}
val aliases: Enumeration<String> = ks.aliases()
/*
 * Load the Android KeyStore instance using the
 * AndroidKeyStore provider to list the currently stored entries.
 */
KeyStore ks = KeyStore.getInstance("AndroidKeyStore");
ks.load(null);
Enumeration<String> aliases = ks.aliases();

توقيع البيانات وإثبات صحتها

يمكنك توقيع البيانات من خلال جلب KeyStore.Entry من ملف تخزين المفاتيح واستخدام واجهات برمجة التطبيقات Signature، مثل sign():

KotlinJava
/*
 * Use a PrivateKey in the KeyStore to create a signature over
 * some data.
 */
val ks: KeyStore = KeyStore.getInstance("AndroidKeyStore").apply {
    load(null)
}
val entry: KeyStore.Entry = ks.getEntry(alias, null)
if (entry !is KeyStore.PrivateKeyEntry) {
    Log.w(TAG, "Not an instance of a PrivateKeyEntry")
    return null
}
val signature: ByteArray = Signature.getInstance("SHA256withECDSA").run {
    initSign(entry.privateKey)
    update(data)
    sign()
}
/*
 * Use a PrivateKey in the KeyStore to create a signature over
 * some data.
 */
KeyStore ks = KeyStore.getInstance("AndroidKeyStore");
ks.load(null);
KeyStore.Entry entry = ks.getEntry(alias, null);
if (!(entry instanceof PrivateKeyEntry)) {
    Log.w(TAG, "Not an instance of a PrivateKeyEntry");
    return null;
}
Signature s = Signature.getInstance("SHA256withECDSA");
s.initSign(((PrivateKeyEntry) entry).getPrivateKey());
s.update(data);
byte[] signature = s.sign();

وبالمثل، يمكنك التحقّق من البيانات باستخدام طريقة verify(byte[]):

KotlinJava
/*
 * Verify a signature previously made by a private key in the
 * KeyStore. This uses the X.509 certificate attached to the
 * private key in the KeyStore to validate a previously
 * generated signature.
 */
val ks = KeyStore.getInstance("AndroidKeyStore").apply {
    load(null)
}
val entry = ks.getEntry(alias, null) as? KeyStore.PrivateKeyEntry
if (entry == null) {
    Log.w(TAG, "Not an instance of a PrivateKeyEntry")
    return false
}
val valid: Boolean = Signature.getInstance("SHA256withECDSA").run {
    initVerify(entry.certificate)
    update(data)
    verify(signature)
}
/*
 * Verify a signature previously made by a private key in the
 * KeyStore. This uses the X.509 certificate attached to the
 * private key in the KeyStore to validate a previously
 * generated signature.
 */
KeyStore ks = KeyStore.getInstance("AndroidKeyStore");
ks.load(null);
KeyStore.Entry entry = ks.getEntry(alias, null);
if (!(entry instanceof PrivateKeyEntry)) {
    Log.w(TAG, "Not an instance of a PrivateKeyEntry");
    return false;
}
Signature s = Signature.getInstance("SHA256withECDSA");
s.initVerify(((PrivateKeyEntry) entry).getCertificate());
s.update(data);
boolean valid = s.verify(signature);

طلب مصادقة المستخدم لاستخدام المفتاح

عند إنشاء مفتاح أو استيراده إلى AndroidKeyStore، يمكنك تحديد أنّه ليس مسموحًا باستخدام المفتاح إلا إذا تمّت مصادقة المستخدم. تتم مصادقة المستخدم باستخدام مجموعة فرعية من بيانات اعتماد شاشة القفل الآمنة (النقش/رقم التعريف الشخصي/كلمة المرور، بيانات الاعتماد للمقاييس الحيوية).

هذه ميزة أمان متقدّمة لا تكون مفيدة بشكل عام إلا إذا كانت متطلباتك هي أنّه ينبغي عدم السماح بتجاوز شرط مصادقة المستخدم لاستخدام المفتاح في حال اختراق عملية تقديم طلبك بعد إنشاء المفتاح أو استيراده (وليس قبل ذلك أو أثناء ذلك).

عندما لا يُسمح باستخدام مفتاح إلا إذا تمّت مصادقة المستخدم، يمكنك الاتصال بـ setUserAuthenticationParameters() لضبطه للعمل في أحد الأوضاع التالية:

منح الإذن لفترة زمنية
يتم تفويض استخدام جميع المفاتيح فور مصادقة المستخدم باستخدام إحدى بيانات الاعتماد المحدّدة.
التفويض لمدة عملية تشفير معيّنة

يجب أن يوافق المستخدم بشكلٍ فردي على كل عملية تتضمن مفتاحًا معيّنًا.

يبدأ تطبيقك هذه العملية من خلال استدعاء authenticate() على مثيل BiometricPrompt.

بالنسبة إلى كل مفتاح تنشئه، يمكنك اختيار السماح باستخدام بيانات اعتماد قوية مستندة إلى المقاييس الحيوية أو بيانات اعتماد خاصة بشاشة القفل أو كلا النوعَين من بيانات الاعتماد. لتحديد ما إذا كان المستخدم قد أعدّ بيانات الاعتماد التي يعتمد عليها مفتاح تطبيقك، اتصل canAuthenticate().

إذا كان المفتاح يتيح بيانات الاعتماد الحيوية فقط، يتم إبطال صلاحيته تلقائيًا عند إضافة عمليات تسجيل جديدة بالمقاييس الحيوية. يمكنك ضبط المفتاح ليظل صالحًا عند إضافة عمليات تسجيل جديدة باستخدام ميزات قياس البصمات الحيوية. لإجراء ذلك، مرِّر false في setInvalidatedByBiometricEnrollment().

تعرَّف على مزيد من المعلومات عن كيفية إضافة ميزات المصادقة باستخدام المقاييس الحيوية إلى تطبيقك، بما في ذلك كيفية عرض مربّع حوار المصادقة باستخدام المقاييس الحيوية.

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

الخطوات التالية