
كثيرا ما أسمع أسئلة مثل "كيفية تنفيذ المصادقة في تطبيق Android؟" ، "أين يمكنني تخزين رمز PIN؟" ، "يا رجل ، هل سأكون آمنًا إذا قمت بتطبيق ميزة مصادقة بهذه الطريقة؟" والكثير من هذا النوع. لقد تعبت حقًا من الإجابة على هذه الأسئلة ، لذلك قررت أن أكتب كل أفكاري حولها مرة واحدة لمشاركتها مع جميع الأسئلة.
جدول المحتويات
المصادقة: لماذا يجب علي القيام بذلك؟
لنبدأ من التعريف. التوثيق (من اليونانية: αὐθεντικός authentikos ، "حقيقي ، حقيقي" ، من αὐθέντης authentes ، "مؤلف") هو عمل لإثبات التأكيد ، مثل هوية مستخدم نظام الكمبيوتر.
لذلك ، إذا كان التطبيق الخاص بك يحتوي على معلومات حساسة (أي معلومات المستخدم حساسة IMHO) عليك إضافة سيناريو مصادقة إلى التطبيق لمنع الوصول غير المصرح به إلى هذه المعلومات.
سيناريوهات المصادقة الأكثر شعبية كالتالي:
- تسجيل الدخول + كلمة المرور
- كلمة السر الرئيسية
- رقم التعريف الشخصي (4 أرقام أو أكثر)
- القياسات الحيوية
وبطبيعة الحال ، تأتي مصادقة تسجيل الدخول وكلمة المرور إلى التطبيق الخاص بك من النهاية الخلفية وأمن هذه الآلية سنتركه لفريق ضمان الأمان الخلفي ؛) لا تنسَ تطبيق دبوس المفاتيح العامة .
نادرًا ما يتم استخدام مصادقة كلمة المرور الرئيسية وفقط في التطبيقات التي تتطلب مستوى عالًا من الأمان (مثل مديري كلمة المرور).
وبالتالي ، لدينا سيناريوهان أكثر شيوعًا: رقم التعريف الشخصي والقياسات الحيوية . إنها سهلة الاستخدام وسهلة التنفيذ نسبيًا (في الواقع أنها ليست ...). سنغطي في هذه المقالة الجوانب الرئيسية للتطبيق الصحيح لهذه الميزات.
طريقة بسيطة
فقط تخيل أنك مطور نظام أندرويد وشفراتك تطبعك بالمال. لا تقلق بشأن أي شيء ، ولا تحتاج بشدة إلى الخبرة الأمنية الجادة لتطبيقات الأجهزة المحمولة. لكن في يوم من الأيام ، يأتي إليك المدير ويقوم بمهمة "تنفيذ مصادقة إضافية عبر رمز PIN وبصمة في طلبنا". تبدأ القصة هنا ...
لتطبيق مصادقة PIN ، ستقوم بإنشاء شاشتين مثل هذه:


واكتب هذه الشفرة لإنشاء رقم PIN والتحقق منه
fun savePin(pin: String) { preferences.edit().putString(StorageKey.PIN, pin).apply() }
fun authenticate(pin: String) { authenticationState.value = if (pinIsValid(pin)) { AuthenticationState.AUTHENTICATED } else { AuthenticationState.INVALID_AUTHENTICATION } } private fun pinIsValid(pin: String): Boolean { return preferences.getString(StorageKey.PIN, null) == pin }
هذا كل شئ! الآن ، لديك نظام مصادقة رائع عبر رمز PIN. ألف مبروك. كان الأمر سهلاً ، أليس كذلك؟
بالطبع ، لقد اشتعلت بالفعل المفارقة في كلماتي. هذه الطريقة سيئة للغاية لأنه يتم تخزين رمز PIN كنص عادي. إذا تمكنت البرامج الضارة من الوصول إلى سعة تخزين التطبيقات الداخلية بطريقة ما ، فستحصل على رقم التعريف الشخصي للمستخدم كما هو. يمكنك أن تسألني "لماذا هذا سيء جدًا؟ إنه مجرد رقم تعريف شخصي من المصادقة المحلية ...". نعم ، لكن المستخدمين يميلون إلى تعيين نفس رقم التعريف الشخصي في كل مكان. لذلك ، تتيح معرفة رقم PIN للمستخدم للمتطفل توسيع سطح الهجوم.
علاوة على ذلك ، لا يتيح لك نظام المصادقة هذا تنفيذ تشفير بيانات المستخدم استنادًا إلى رقم PIN بطريقة آمنة (سنتحدث عنه لاحقًا).
لنجعلها أفضل
كيف يمكننا تحسين تطبيقنا السابق؟ النهج الأول والواضح هو أخذ علامة تجزئة من رقم التعريف الشخصي وتخزين هذه التجزئة.
دالة التجزئة هي أي وظيفة يمكن استخدامها لتعيين بيانات ذات حجم تعسفي إلى قيم ذات حجم ثابت. تسمى القيم التي يتم إرجاعها بواسطة دالة هاش قيم التجزئة أو أكواد التجزئة أو الهضم أو التجزئة ببساطة. تُستخدم القيم لفهرسة جدول ذي حجم ثابت يسمى جدول التجزئة. يُطلق على استخدام دالة هاش لفهرسة جدول تجزئة عنونة تجزئة أو تخزين مبعثر.
هناك الكثير من وظائف التجزئة المتاحة في Android Framework (في Java Cryptography Architecture ، بدقة) ، ولكن اليوم لا يعتبر كل منها آمنًا. لا أوصي باستخدام MD5 و SHA-1 بسبب الاصطدامات. SHA-256 هو خيار جيد لمعظم المهام.
fun sha256(byteArray: ByteArray): ByteArray { val digest = try { MessageDigest.getInstance("SHA-256") } catch (e: NoSuchAlgorithmException) { MessageDigest.getInstance("SHA") } return with(digest) { update(byteArray) digest() } }
savePin(...)
طريقة savePin(...)
الخاصة بنا لتخزين PIN المجزأة
fun savePin(pin: String) { val hashedPin = sha256(pin.toByteArray()) val encodedHash = Base64.encodeToString(hashedPin, Base64.DEFAULT) preferences.edit().putString(StorageKey.PIN, encodedHash).apply() }
استخدام التجزئة هو بداية جيدة ، ولكن التجزئة العارية ليست كافية لمهمتنا. في الحياة الواقعية ، قام المهاجم بالفعل بحساب جميع رموز PIN المكونة من 4 أرقام. سيكون قادرًا على فك تشفير كل أرقام PIN المسروقة هذه بسهولة تامة. هناك نهج للتعامل معها - ملح .
في التشفير ، الملح عبارة عن بيانات عشوائية تُستخدم كمدخلات إضافية لوظيفة أحادية الاتجاه "تجزئة" البيانات أو كلمة المرور أو عبارة المرور. يتم استخدام الأملاح لحماية كلمات المرور في التخزين. تاريخياً ، كانت كلمة المرور مخزنة في نص عادي على نظام ما ، ولكن مع مرور الوقت تم تطوير ضمانات إضافية لحماية كلمة مرور المستخدم من قراءتها من النظام. الملح هو واحد من تلك الطرق.
لإضافة ملح إلى آلية الأمان الخاصة بنا ، نحتاج إلى تغيير الكود الموضح أعلاه بهذه الطريقة
fun generate(lengthByte: Int = 32): ByteArray { val random = SecureRandom() val salt = ByteArray(lengthByte) random.nextBytes(salt) return salt }
fun savePin(pin: String) { val salt = Salt.generate() val saltedPin = pin.toByteArray() + salt val hashedPin = Sha256.hash(saltedPin) val encodedHash = Base64.encodeToString(hashedPin, Base64.DEFAULT) val encodedSalt = Base64.encodeToString(salt, Base64.DEFAULT) preferences.edit() .putString(StorageKey.PIN, encodedHash) .putString(StorageKey.SALT, encodedSalt) .apply() }
لاحظ أنه يتعين عليك تخزين الملح مع رقم التعريف الشخصي لأنك تحتاج إلى حساب التجزئة الناتج (باستخدام الملح) في كل مرة عند التحقق من رقم التعريف الشخصي من إدخال المستخدم.
private fun pinIsValid(pin: String): Boolean { val encodedSalt = preferences.getString(StorageKey.SALT, null) val encodedHashedPin = preferences.getString(StorageKey.PIN, null) val salt = Base64.decode(encodedSalt, Base64.DEFAULT) val storedHashedPin = Base64.decode(encodedHashedPin, Base64.DEFAULT) val enteredHashedPin = Sha256.hash(pin.toByteArray() + salt) return storedHashedPin contentEquals enteredHashedPin }
كما ترون ، لا يزال من الصعب فهم الكود ، لكن أمن هذا الحل أصبح أقوى بكثير. سأقول أكثر من ذلك ، هذا النهج جاهز للإنتاج لمعظم التطبيقات التي لا تتطلب مستوى عالًا من الأمان.
"لكن ماذا لو كنت بحاجة إلى حل أكثر أمانًا؟" ، تسأل. حسنا ، اتبعني.
الطريق الصحيح
دعونا نناقش العديد من نقاط التحسين لنهج المصادقة الخاص بنا.
أولاً ، يتمثل العيب الرئيسي في "التجزئة العادية" (وحتى "التجزئة العادية المملحة") في سرعة عالية نسبيًا في هجوم القوة الغاشمة (حوالي مليارات من التجزئات في الدقيقة ). للتخلص من هذا العيب ، يتعين علينا استخدام وظيفة KDF خاصة مثل PBKDF2 المدعومة أصلاً بواسطة Android Framework. بالطبع ، هناك بعض الاختلاف بين وظائف KDF ، وربما تحتاج إلى اختيار الوظيفة الأخرى ، لكنها خارج نطاق المقالة. سأعطيك العديد من الروابط المفيدة حول هذا الموضوع في نهاية المقال.
ثانياً ، ليس لدينا تشفير بيانات المستخدم في هذه المرحلة. هناك الكثير من الطرق لتنفيذه وسأظهر لك أبسط الطرق وأكثرها موثوقية. ستكون مجموعة من مكتبتين وبعض الأكواد من حولهم.
دعنا نكتب مصنع إنشاء مفتاح PBKDF2 لتبدأ.
object Pbkdf2Factory { private const val DEFAULT_ITERATIONS = 10000 private const val DEFAULT_KEY_LENGTH = 256 private val secretKeyFactory by lazy { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { SecretKeyFactory.getInstance("PBKDF2withHmacSHA1") } else { SecretKeyFactory.getInstance("PBKDF2withHmacSHA256") } } fun createKey( passphraseOrPin: CharArray, salt: ByteArray, iterations: Int = DEFAULT_ITERATIONS, outputKeyLength: Int = DEFAULT_KEY_LENGTH ): SecretKey { val keySpec = PBEKeySpec(passphraseOrPin, salt, iterations, outputKeyLength) return secretKeyFactory.generateSecret(keySpec) } }
الآن مسلحين بهذا المصنع ، يتعين علينا إعادة savePin()
أساليبنا savePin()
و pinIsValid()
fun savePin(pin: String) { val salt = Salt.generate() val secretKey = Pbkdf2Factory.createKey(pin.toCharArray(), salt) val encodedKey = Base64.encodeToString(secretKey.encoded, Base64.DEFAULT) val encodedSalt = Base64.encodeToString(salt, Base64.DEFAULT) preferences.edit() .putString(StorageKey.KEY, encodedKey) .putString(StorageKey.SALT, encodedSalt) .apply() pinIsCreated.value = true }
private fun pinIsValid(pin: String): Boolean { val encodedSalt = preferences.getString(StorageKey.SALT, null) val encodedKey = preferences.getString(StorageKey.KEY, null) val salt = Base64.decode(encodedSalt, Base64.DEFAULT) val storedKey = Base64.decode(encodedKey, Base64.DEFAULT) val enteredKey = Pbkdf2Factory.createKey(pin.toCharArray(), salt) return storedKey contentEquals enteredKey.encoded }
وبالتالي ، لقد خففنا للتو العيب الرئيسي في حلنا السابق. إنه جيد ، والآن يتعين علينا إضافة تشفير بيانات المستخدم. لتنفيذه ، سنأخذ هذه المكتبات:
- Tink - مكتبة متعددة اللغات وعبر منصات توفر واجهات برمجة التطبيقات للتشفير آمنة وسهلة الاستخدام بشكل صحيح ويصعب إساءة استخدامها.
- ؛ Jetpack Security - قراءة وكتابة الملفات المشفرة والتفضيلات المشتركة باتباع أفضل ممارسات الأمان.
للحصول على سعة تخزين مشفرة جيدة ، يتعين علينا كتابة هذا الرمز:
class App : Application() { ... val encryptedStorage by lazy { EncryptedSharedPreferences.create( "main_storage", "main_storage_key", this, EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM ) } ... }
هذا كل شيء. في وقت لاحق ، يمكننا التعامل معها كما لو كانت SharedPreferences
منتظمة ، ولكن سيتم تشفير جميع البيانات. الآن يمكننا بسهولة استبدال التنفيذ السابق.
class CreatePinViewModel(application: Application) : AndroidViewModel(application) { ... private val preferences by lazy { getApplication<App>().encryptedStorage } ... }
class InputPinViewModel(application: Application) : AndroidViewModel(application) { ... private val preferences by lazy { getApplication<App>().encryptedStorage } ... }
لنلخص المجموع الفرعي. لدينا مفتاح آمن تمامًا مستمد من رمز PIN ، ونهج موثوق به إلى حد ما لتخزينه. يبدو ذلك رائعًا ، لكن ليس كافيًا. ماذا لو افترضنا أن المهاجم حصل على حق الوصول إلى الجهاز الخاص بنا واستخلص البيانات كاملة منه. من الناحية النظرية ، لديه كل المكونات لفك تشفير البيانات في هذه اللحظة. لحل هذه المشكلة ، يتعين علينا تحقيق أمرين:
- لا يتم تخزين رقم التعريف الشخصي على الإطلاق
- تعتمد عمليات التشفير على PIN
كيف يمكننا تحقيق هذه الأهداف دون إعادة كتابة الكود بأكمله؟ انه سهل! طالما أننا نستخدم Tink ، يمكننا تطبيق ميزة التشفير الخاصة بها المسماة بالبيانات المرتبطة.
البيانات المرتبطة المراد مصادقتها ، ولكن غير مشفرة. البيانات المرتبطة اختيارية ، لذلك يمكن أن تكون هذه المعلمة خالية. في هذه الحالة ، تكون القيمة الخالية مكافئة لمصفوفة بايت فارغة (ذات طول صفري). من أجل فك التشفير الناجح ، يجب توفير نفس البيانات المقترنة مع النص المشفر.
هذا كل شيء! يمكننا استخدام رقم التعريف الشخصي كبيانات مرتبطة لتحقيق أهدافنا المعينة. وبالتالي ، فإن إمكانية أو استحالة فك تشفير بيانات المستخدم ستكون بمثابة مؤشر على صحة رقم التعريف الشخصي. هذا المخطط يعمل عادة على النحو التالي:

إذا قام أحد المستخدمين بإدخال رقم PIN غير صحيح ، فستتلقى GeneralSecurityException عند محاولة فك تشفير رمز الوصول. لذلك ، قد يبدو التنفيذ النهائي كالتالي:
اظهار الكود class CreatePinViewModel(application: Application): AndroidViewModel(application) { ... private val fakeAccessToken = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzZWNyZXQiOiJXZSdyZSBoaXJpbmcgOykifQ.WZrEWG-l3VsJzJrbnjn2BIYO68gHIGyat6jrw7Iu-Rw" private val preferences by lazy { getApplication<App>().encryptedStorage } private val aead by lazy { getApplication<App>().pinSecuredAead } ... fun savePin(pin: String) { val salt = Salt.generate() val secretKey = Pbkdf2Factory.createKey(pin.toCharArray(), salt) val encryptedToken = aead.encrypt( fakeAccessToken.toByteArray(), secretKey.encoded ) preferences.edit { putString(StorageKey.TOKEN, Base64.encodeToString( encryptedToken, Base64.DEFAULT )) putString(StorageKey.SALT, Base64.encodeToString(salt, Base64.DEFAULT)) putBoolean(StorageKey.PIN_IS_ENABLED, true) } ... } }
class InputPinViewModel(application: Application) : AndroidViewModel(application) { ... private val preferences by lazy { getApplication<App>().encryptedStorage } private val aead by lazy { getApplication<App>().pinSecuredAead } fun authenticate(pin: String) { authenticationState.value = if (pinIsValid(pin)) { AuthenticationState.AUTHENTICATED } else { AuthenticationState.INVALID_AUTHENTICATION } } private fun pinIsValid(pin: String): Boolean { val salt = Base64.decode( preferences.getString(StorageKey.SALT, null), Base64.DEFAULT ) val secretKey = Pbkdf2Factory.createKey(pin.toCharArray(), salt) val token = try { val encryptedToken = Base64.decode( preferences.getString(StorageKey.TOKEN, null), Base64.DEFAULT ) aead.decrypt(encryptedToken, secretKey.encoded) } catch (e: GeneralSecurityException) { null } return token?.isNotEmpty() ?: false } }
نتيجة لطيفة! الآن لم نعد نقوم بتخزين PIN ، ويتم تشفير جميع البيانات بشكل افتراضي. بالطبع ، هناك الكثير من الطرق لتحسين هذا التطبيق إذا كنت ترغب في ذلك. لقد أظهرت فقط المبدأ الأساسي.
لكن مهلا ، ماذا عن القياسات الحيوية؟
لا أعتقد أن "القياسات الحيوية" تتعلق بالأمن. أفضل تسميته "ميزة مستخدم مريحة للغاية". إنها حرب مقدسة قديمة بشكل رهيب بين الراحة والأمن. لكن معظم المستخدمين يعجبهم هذا النوع من المصادقة ويتعين علينا كمطورين تطبيقه بأمان قدر الإمكان.
لسوء الحظ ، تنفيذ مصادقة البيومترية صعب للغاية. لهذا السبب سأبدأ بعرض بعض مبادئ التنفيذ المشتركة وإعطاء بعض التفسيرات. بعد هذا سنغوص في عمق الكود.

يحتوي هذا المخطط على فارق بسيط واحد هام: يتم حفظ المفتاح السري على القرص . بالطبع ليس كنص عادي ، ولكن مع ذلك.
كما ترون ، لقد أنشأنا مفتاح تشفير جديد في لوحة المفاتيح ونستخدم هذا المفتاح لتشفير مفتاحنا السري المشتق من رمز PIN. يسمح لنا مثل هذا المخطط بعدم إعادة تشفير جميع البيانات عند تغيير طريقة المصادقة. علاوة على ذلك ، لا تزال لدينا القدرة على إدخال رمز PIN إذا فشلت المصادقة البيومترية لأي سبب من الأسباب. حسنًا ، دعنا نكتب الكثير من التعليمات البرمجية.
أولاً ، سأعرض التغييرات في تدفق إنشاء رقم التعريف الشخصي:
اظهار الكود class CreatePinViewModel(application: Application): AndroidViewModel(application) { companion object { private const val ANDROID_KEY_STORE = "AndroidKeyStore" private const val KEY_NAME = "biometric_key" } ... val biometricEnableDialog = MutableLiveData<SingleLiveEvent<Unit>>() val biometricParams = MutableLiveData<BiometricParams>() val authenticationCallback = object : BiometricPrompt.AuthenticationCallback() { override fun onAuthenticationError(errorCode: Int, errString: CharSequence) { super.onAuthenticationError(errorCode, errString) } override fun onAuthenticationSucceeded(result: AuthenticationResult) { super.onAuthenticationSucceeded(result) val encryptedSecretKey = result.cryptoObject?.cipher?.doFinal( secretKey.encoded ) preferences.edit { putString(StorageKey.KEY, Base64.encodeToString( encryptedSecretKey, Base64.DEFAULT )) } pinIsCreated.postValue(true) } override fun onAuthenticationFailed() { super.onAuthenticationFailed() } } ... private val biometricManager by lazy { getApplication<App>().biometricManager } private val keyStore = KeyStore.getInstance(ANDROID_KEY_STORE) private lateinit var secretKey: SecretKey ... fun enableBiometric(isEnabled: Boolean) { generateKey() val cipher = createCipher().also { preferences.edit { putString(StorageKey.KEY_IV, Base64.encodeToString(it.iv, Base64.DEFAULT)) } } val promptInfo = createPromptInfo() val cryptoObject = BiometricPrompt.CryptoObject(cipher) if (isEnabled) { biometricParams.value = BiometricParams(isEnabled, promptInfo, cryptoObject) } else { pinIsCreated.value = true } } private fun createPromptInfo(): BiometricPrompt.PromptInfo { return BiometricPrompt.PromptInfo.Builder() .setTitle("Create biometric authorization") .setSubtitle("Touch your biometric sensor") .setNegativeButtonText("Cancel") .build() } private fun generateKey() { try { keyStore.load(null) val keyProperties = PURPOSE_ENCRYPT or PURPOSE_DECRYPT val builder = KeyGenParameterSpec.Builder(KEY_NAME, keyProperties) .setBlockModes(BLOCK_MODE_CBC) .setUserAuthenticationRequired(true) .setEncryptionPaddings(ENCRYPTION_PADDING_NONE) val keyGenerator = KeyGenerator.getInstance( KEY_ALGORITHM_AES, ANDROID_KEY_STORE ) keyGenerator.run { init(builder.build()) generateKey() } } catch (e: Exception) { authenticationCallback.onAuthenticationError( BiometricConstants.ERROR_NO_DEVICE_CREDENTIAL, e.localizedMessage ) } } private fun createCipher(): Cipher { val key = with(keyStore) { load(null) getKey(KEY_NAME, null) } return Cipher.getInstance( "$KEY_ALGORITHM_AES/$BLOCK_MODE_CBC/$ENCRYPTION_PADDING_NONE" ).apply { init(Cipher.ENCRYPT_MODE, key) } } }
سأكون مسرورًا إذا قامت Google بتضمين Tink في القياسات الحيوية ، ولكن ... علينا أن نكتب هذا الرمز المعزز مع Cipher و KeyStore. هذا الرمز مألوف تمامًا لأولئك الأشخاص الذين يعملون مع تشفير في Android ، لكنني أود الانتباه إلى منصات التشفير. نعم ، لمنع هجوم Padding Oracle ، لا نستخدم الحشو مطلقًا. وبالتالي ، فإننا نخفف المخاطر عند تخزين المفتاح السري على القرص.
رمز التحقق من القياس الحيوي مشابه جدًا:
اظهار الكود class InputPinViewModel(application: Application) : AndroidViewModel(application) { companion object { private const val ANDROID_KEY_STORE = "AndroidKeyStore" private const val KEY_NAME = "biometric_key" } ... val biometricErrorMessage = MutableLiveData<SingleLiveEvent<String>>() val biometricParams = MutableLiveData<BiometricParams>() ... private val biometricManager by lazy { getApplication<App>().biometricManager } private val keyStore = KeyStore.getInstance(ANDROID_KEY_STORE) val authenticationCallback = object : BiometricPrompt.AuthenticationCallback() { override fun onAuthenticationError(errorCode: Int, errString: CharSequence) { super.onAuthenticationError(errorCode, errString) } override fun onAuthenticationSucceeded(result: AuthenticationResult) { super.onAuthenticationSucceeded(result) val encryptedSecretKey = Base64.decode( preferences.getString(StorageKey.KEY, ""), Base64.DEFAULT ) val secretKey = result.cryptoObject?.cipher?.doFinal(encryptedSecretKey) val token = try { val encryptedToken = Base64.decode( preferences.getString(StorageKey.TOKEN, null), Base64.DEFAULT ) aead.decrypt(encryptedToken, secretKey) } catch (e: GeneralSecurityException) { null } val state = if (token?.isNotEmpty() == true) { AuthenticationState.AUTHENTICATED } else { AuthenticationState.INVALID_AUTHENTICATION } authenticationState.postValue(state) } override fun onAuthenticationFailed() { super.onAuthenticationFailed() } } ... fun biometricAuthenticate() { if (preferences.contains(StorageKey.KEY)) { when (biometricManager.canAuthenticate()) { BiometricManager.BIOMETRIC_SUCCESS -> { val promptInfo = createPromptInfo() val cryptoObject = BiometricPrompt.CryptoObject(createCipher()) biometricParams.value = BiometricParams(promptInfo, cryptoObject) } } } else { biometricErrorMessage.value = SingleLiveEvent( "Biometric authentication isn't configured" ) } } ... private fun createPromptInfo(): BiometricPrompt.PromptInfo { return BiometricPrompt.PromptInfo.Builder() .setTitle("Biometric login for my app") .setSubtitle("Log in using your biometric credential") .setNegativeButtonText("Cancel") .build() } private fun createCipher(): Cipher { val key = with(keyStore) { load(null) getKey(KEY_NAME, null) } return Cipher.getInstance( "$KEY_ALGORITHM_AES/$BLOCK_MODE_CBC/$ENCRYPTION_PADDING_NONE" ).apply { val iv = Base64.decode( preferences.getString(StorageKey.KEY_IV, null), Base64.DEFAULT ) init(Cipher.DECRYPT_MODE, key, IvParameterSpec(iv)) } } }
انتبه إلى authenticationCallback.onAuthenticationSucceeded
، فهو يحتوي على منطق مفتاح المصادقة بعد القياس الحيوي. في الحقيقة ، هذا تطبيق بديل pinIsValid()
. إذا لم يكن لديك فهم قوي لما يحدث في كتلتين رمزيتين سابقتين ، فيرجى الرجوع إلى الوثائق الرسمية للقياسات الحيوية .
هل أنا محمي تمامًا؟
لقد قمنا بالكثير من الأشياء الرائعة لتحقيق المصادقة باستخدام رمز PIN والقياسات الحيوية ، ولكن هل هذا موثوق وآمن؟ بالطبع ، لقد بذلنا قصارى جهدنا ، ولكن هناك بضع نقاط يجب مراعاتها.
يتكون رقم PIN الكلاسيكي من أربعة أرقام فقط ، كما أن إنتروبيا منخفضة للغاية. لذلك ، مثل هذا النوع من التعليمات البرمجية ليست آمنة تماما للاستخدام. على الرغم من كل ما قمنا به ، هناك احتمال أن يتمكن المتسلل من كسر هذا الرمز. نعم ، عليه أن يحقق الهندسة العكسية للتطبيق الخاص بك وفهم كيف تقوم بتشفير بيانات المستخدم ، ولكن مع ذلك. إذا تم تحفيز المهاجم بما يكفي فسوف يفعل ذلك دون تردد.
النقطة الثانية هي حول الهواتف الذكية الجذور. عندما يتعلق الأمر بالأجهزة ذات الجذور ، يمكنك التخلص من جميع محاولات ضمان الأمان. أي البرمجيات الخبيثة مع الوصول إلى الجذر قادرة على تجاوز جميع آليات الأمان. لذلك ، يجب عليك إضافة ميزات أمان إضافية والتحققات إلى التطبيق. أقترح عليك شيئين أبسط لتخفيف هذه العيوب:
- SafetyNet - يوفر مجموعة من الخدمات وواجهات برمجة التطبيقات التي تساعد في حماية تطبيقك من تهديدات الأمان ، بما في ذلك التلاعب بالأجهزة وعناوين URL السيئة والتطبيقات التي يحتمل أن تكون ضارة والمستخدمين المزيفين
- التشويش - يرجى تذكر أن ProGuard ليس أداة تشويش! ProGuard يدور حول تقليل الموارد وتقليصها ، وليس التشويش أو الأمان. استخدم شيئًا مثل DexGuard و DexProtector ، إلخ.
يعد استخدام SafetyNet والتعتيم خطوة جيدة بعد تطبيق النهج من هذه المقالة. إذا لاحظت عدم دقة أو عيوب أمنية أو هراء آخر ، فيرجى إخبارنا بذلك. يمكنك العثور على جميع التعليمات البرمجية من المقالة على جيثب .
وفي المرة القادمة سأوضح لك كيفية تنفيذ مصادقة PIN باستخدام الخلفية. ترقبوا.
روابط مفيدة