前言
最近,在查看 SampleProject 这个项目的时候,就觉得吧,这个登录有点麻烦,总是要输密码,现在很多 APP 都是可以指纹登录的呀,这个必须支持一波;而且开发这么多年还没尝试过指纹识别,这可不行,学到老活到老嘛。
指纹登录流程
指纹登录 不就是简单的调用 指纹识别 的 API
然后登录账号吗,这有什么可说的?可能有人会问了。然而并非如此,一开始我也很天真的以为就这么简单,但是在网上找了很多文章之后发现,大多数的文章都只是详细的说明了怎么去调用 指纹识别 的 API
,至于怎么用于登录,怎么去实现 指纹登录 的业务确是很少提及,所以,这里我会先给大家讲清楚实现 指纹登录 的流程再去实现。
指纹识别在指纹登录中的作用
登录是我们应用中的逻辑,我们把 指纹识别 穿插在其中是需要他做什么?
实际上,我们可以把 指纹登录 简单的理解成 不用密码登录,那么为了能过 不用密码登录,我们肯定需要把用户登录需要的信息 保存到本地
,那么我们首先考虑到的就是 本地数据的安全问题,在这里,指纹识别 就为我们本地数据提供了 加密、解密
的功能。
指纹登录相关流程
![指纹登录流程](http://cloud.wangjie0822.top/img/blog/bef2e009/flow.png)
如上图所示,在使用 指纹登录 功能前,我们需要先 开启指纹登录,这个步骤是为了获取登录所需要的数据,并进行加密存储,之后再 指纹登录 时,获取存储加密的数据,进行解密,然后进行登录。
指纹识别面临的问题
在实现功能之前,我们需要知道,Google 从 Android 6.0
才开始支持 指纹识别,所以,如果你想兼顾 6.0 以下的机型,那么你可能需要自己去集成不同 手机厂商 的 SDK
,当然了,现在 6.0
以下的手机已经很少了,而且我的项目只是一个自用的 DEMO,就不去做那些复杂的东西了,有需要的可以自行了解;
其次,从 Android 6.0
Google 新增了 FingerprintManager
用于指纹识别,后续又新增了 FingerprintManagerCompat
提供了一些兼容性操作,再到 Android 9.0
新增了 BiometricPrompt
API
用于生物识别,并将 FingerprintManager
添加了 @Deprecated
标记,所以在实现时我们也需要考虑版本兼容问题。
指纹登录实现
从上面,我们了解了 指纹登录 的流程以及 指纹识别 的发展及要注意的问题,接下来我们开始实现。
指纹识别的集成
首先,我们将 指纹识别 功能集成进来。
虽然 Android 6.0
就有了 指纹识别 的 API
,但显然并不是所有手机都会支持,所以,我们首先来判断当前手机是否支持 指纹识别。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| private val fingerprintManager: FingerprintManagerCompat by lazy { FingerprintManagerCompat.from(activity) }
fun checkBiometric(): Int { val km = context.getSystemService(KeyguardManager::class.java) return when { !fingerprintManager.isHardwareDetected -> { BiometricInterface.ERROR_HW_UNAVAILABLE } !km.isKeyguardSecure -> { BiometricInterface.ERROR_NO_DEVICE_CREDENTIAL } !fingerprintManager.hasEnrolledFingerprints() -> { BiometricInterface.ERROR_NO_BIOMETRICS } else -> { BiometricInterface.HW_AVAILABLE } } }
|
在确定手机支持 指纹识别 后,我们就可以调用 API
拉起指纹识别功能了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| private val fingerprintManager: FingerprintManagerCompat by lazy { FingerprintManagerCompat.from(activity) }
fun authenticateM() { fingerprintManager.authenticate( crypto, flags, cancel, callback, handler ) }
|
上面只是 Android 6.0
以上的简单的 指纹认证 代码,但是 FingerprintManagerCompat
并没有提供相关提示弹窗,所以,在这基础上,我们还需要加上相关弹窗逻辑。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| fun authenticateM() { val cancellationSignal = CancellationSignal() cancellationSignal.setOnCancelListener { } val dialog = BiometricDialog.create() dialog.setOnCancelListener { cancellationSignal.cancel() } dialog.show() fingerprintManager.authenticate( crypto, flags, cancellationSignal, object: FingerprintManagerCompat.AuthenticationCallback() { override fun onAuthenticationSucceeded(result: FingerprintManagerCompat.AuthenticationResult?) { dialog.dismiss() } override fun onAuthenticationHelp(helpCode: Int, helpString: CharSequence?) { dialog.setHint(helpString) } override fun onAuthenticationFailed() { dialog.dismiss() } override fun onAuthenticationError(errorCode: Int, errString: CharSequence?) { dialog.dismiss() } }, null ) }
|
这样,我们的认证功能就完成了,而在 Android 9.0
新增的 BiometricPrompt
API
中已经提供相关提示弹窗,所以不需要我们自己手动实现弹窗。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| fun authenticateQ() { val cancellationSignal = CancellationSignal() cancellationSignal.setOnCancelListener { } val prompt = with(BiometricPrompt.Builder(activity)) { setTitle(title) setSubtitle(subTitle) setDescription(hint) setNegativeButton(negative, activity.mainExecutor, { dialog, _ -> dialog?.dismiss() cancellationSignal.cancel() }) build() } prompt.authenticate( crypto, cancellationSignal, executor, callback ) }
|
那么 crypto
怎么获取呢?这个就要结合业务场景来说了,因为 Cipher
对象在用于 加密、解密 时获取的方式是不同的。
开启指纹登录功能
上文有说到过,指纹登录 功能要先提供 开启指纹登录 来保存登录需要的数据,因为 玩Android 没有单独提供相关的 API
,所以这里我们就使用 登录 接口来验证密码的正确性;
所以,首先弹窗提示,让用户输入密码,确认后调用 登录接口 验证密码正确性,确认密码正确后,将密码暂时缓存,拉起 指纹认证;
指纹认证流程在上面已经说过了,这里我们重点介绍 Cipher
对象的获取。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| fun loadCipher(): Cipher { val keyStore = KeyStore.getInstance("AndroidKeyStore") keyStore.load(null) if (!keyStore.containsAlias(keyAlias)) { val keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore") val builder = KeyGenParameterSpec.Builder( keyAlias, KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT) .setBlockModes(KeyProperties.BLOCK_MODE_CBC) .setUserAuthenticationRequired(false) .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7) keyGenerator.init(builder.build()) keyGenerator.generateKey() } val key = keyStore.getKey(keyAlias, null) val cipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/" + KeyProperties.BLOCK_MODE_CBC + "/" + KeyProperties.ENCRYPTION_PADDING_PKCS7) cipher.init(Cipher.ENCRYPT_MODE, key) return cipher }
|
获取到 Cipher
对象后,调用指纹认证,不同版本 CryptoObject
的对象是不同的,直接新建对应对象,将 Cipher
对象传入即可
1 2 3 4 5 6 7 8 9 10 11
| override fun onAuthenticationSucceeded(result: AuthenticationResult?) { val cipher = result?.cryptoObject?.cipher ?: throw RuntimeException("cipher is null!") val encryptInfo = cipher.doFinal(loginInfo.toByteArray()).toHexString() save(encryptInfo) save(cipher.iv.toHexString()) }
|
这样 开启指纹登录 就完成了。
需要注意的有三点:
- 加密时,
Cipher
对象使用 cipher.init(Cipher.ENCRYPT_MODE, key)
进行初始化;
- 指纹认证成功后,使用回调返回
Cipher
对象对数据进行加密;
- 指纹认证成功后,要将
Cipher
对象中的加密向量 iv
保存起来。
指纹登录功能
通过上面开启了 指纹登录 之后,我们就可以在登录页进行 指纹登录 了。
进入登录页后,可以自动拉起 指纹登录 或者用户点击 指纹登录 后拉起。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| fun loadCipher(): Cipher { val keyStore = KeyStore.getInstance("AndroidKeyStore") keyStore.load(null) if (!keyStore.containsAlias(keyAlias)) { val keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore") val builder = KeyGenParameterSpec.Builder( keyAlias, KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT) .setBlockModes(KeyProperties.BLOCK_MODE_CBC) .setUserAuthenticationRequired(false) .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7) keyGenerator.init(builder.build()) keyGenerator.generateKey() } val key = keyStore.getKey(keyAlias, null) val cipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/" + KeyProperties.BLOCK_MODE_CBC + "/" + KeyProperties.ENCRYPTION_PADDING_PKCS7) val ivBytes = get(IV_BYTES).toHexByteArray() val iv = IvParameterSpec(ivBytes) cipher.init(Cipher.DECRYPT_MODE, key, iv) return cipher }
|
和上面开启一样,拉起指纹认证。
1 2 3 4 5 6 7 8 9
| override fun onAuthenticationSucceeded(result: AuthenticationResult?) { val cipher = result?.cryptoObject?.cipher ?: throw RuntimeException("cipher is null!") val logintInfo = cipher.doFinal(get(encryptInfo).toHexByteArray() login(loginInfo) }
|
这样就完成了 指纹登录。
需要注意的有两点:
- 解密时,
Cipher
对象使用 cipher.init(Cipher.DECRYPT_MODE, key, iv)
进行初始化;
- 指纹认证成功后,使用回调返回
Cipher
对象对数据进行解密。
总结
看完全文,你学会怎么集成 指纹登录 功能了吗?我们需要牢记的是,指纹登录 就是一个类似于 记住密码 的功能,在这个过程中使用到了 指纹认证 来对登录信息进行加密解密,使用的都是 Cipher
对象,而用于加密和解密时 Cipher
对象的初始化方式有所不同,解密时需要使用到加密时生成的 IvParameterSpec
加密向量,而由我们初始化出来的 Cipher
对象是无法直接使用的,需要使用 指纹认证 处理之后才能用于加密解密。
想要我的源码吗?想要的话可以全部给你,去找吧!我把所有源码都放在那里!>> SampleProject <<
感谢大家的耐心观看,我是 WangJie0822 ,一个平平凡凡的程序猿,欢迎关注。