Android中指纹识别的使用


/   今日科技快讯   /

近日,微软斥资75亿美元收购了游戏发行商ZeniMax Media,后者旗下的游戏工作室曾推出《毁灭战士》以及《辐射》等知名游戏。这使得微软旗下的游戏工作室达到23家。

/   作者简介   /

本篇文章来自wangjie0822同学投稿,分享了他对Android开发中的指纹识别内容的实践,相信会对大家有所帮助!同时也感谢作者贡献的精彩文章!

wangjie0822的博客地址:

http://www.wangjie0822.top/

/   前言   /

最近,在做项目登录功能的时候,就觉得吧,传统登录有点麻烦,总是要输密码,现在很多 APP 都是可以指纹登录的呀,这个必须支持一波;而且开发这么多年还没尝试过指纹识别,这可不行,学到老活到老嘛。 

/   指纹登录流程   /

指纹登录不就是简单的调用指纹识别的API,然后登录账号吗,这有什么可说的?可能有人会问了。然而并非如此,一开始我也很天真的以为就这么简单,但是在网上找了很多文章之后发现,大多数的文章都只是详细的说明了怎么去调用指纹识别的 API,至于怎么用于登录,怎么去实现指纹登录的业务确是很少提及,所以,这里我会先给大家讲清楚实现指纹登录的流程再去实现。

指纹识别在指纹登录中的作用

最近,在做项目登录功能的时候,就觉登录是我们应用中的逻辑,我们把指纹识别穿插在其中是需要他做什么?

实际上,我们可以把指纹登录简单的理解成不用密码登录,那么为了能过不用密码登录,我们肯定需要把用户登录需要的信息保存到本地,那么我们首先考虑到的就是本地数据的安全问题,在这里,指纹识别就为我们本地数据提供了加密、解密的功能。

指纹登录相关流程

如上图所示,在使用指纹登录功能前,我们需要先开启指纹登录,这个步骤是为了获取登录所需要的数据,并进行加密存储,之后再指纹登录时,获取存储加密的数据,进行解密,然后进行登录。

/   指纹识别面临的问题   /

在实现功能之前,我们需要知道,Google从Android 6.0才开始支持 指纹识别,所以,如果你想兼顾6.0以下的机型,那么你可能需要自己去集成不同手机厂商的 SDK,当然了,现在6.0以下的手机已经很少了,而且我的项目只是一个自用的 DEMO,就不去做那些复杂的东西了,有需要的可以自行了解;

其次,从Android 6.0 Google新增了FingerprintManager用于指纹识别,后续又新增了FingerprintManagerCompat提供了一些兼容性操作,再到Android 9.0新增了BiometricPromptAPI用于生物识别,并将FingerprintManager添加了 @Deprecated 标记,所以在实现时我们也需要考虑版本兼容问题。

/   指纹登录实现   /

从上面,我们了解了指纹登录的流程以及指纹识别的发展及要注意的问题,接下来我们开始实现。

指纹识别的集成

首先,我们将指纹识别功能集成进来。

虽然Android 6.0就有了指纹识别的API,但显然并不是所有手机都会支持,所以,我们首先来判断当前手机是否支持指纹识别。

/** [Build.VERSION_CODES.M] 以上指纹管理对象 */
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拉起指纹识别功能了。

/** [Build.VERSION_CODES.M] 以上指纹管理对象 */
private val fingerprintManager: FingerprintManagerCompat by lazy {
    FingerprintManagerCompat.from(activity)
}

/** [Build.VERSION_CODES.M] 以上拉起指纹认证 */
fun authenticateM() {
    fingerprintManager.authenticate(
        crypto, // 包装了 Cipher 对象的 FingerprintManagerCompat.CryptoObject 对象,用于加解密
        flags, // 可选 flag,建议为 0
        cancel, // CancellationSignal 对象,用于取消指纹认证,可空,但不建议为 null
        callback, // 认证回调接口
        handler // 回调所在 Handler,一般为 null
    )
}

上面只是 Android 6.0以上的简单的指纹认证代码,但是FingerprintManagerCompat并没有提供相关提示弹窗,所以,在这基础上,我们还需要加上相关弹窗逻辑。

/** [Build.VERSION_CODES.M] 以上拉起指纹认证 */
fun authenticateM() {
    val cancellationSignal = CancellationSignal()
    cancellationSignal.setOnCancelListener {
        // 取消回调
    }
    val dialog = BiometricDialog.create()
    dialog.setOnCancelListener {
        // 取消指纹认证
        cancellationSignal.cancel()
    }
    dialog.show()
    fingerprintManager.authenticate(
        crypto, // 包装了 Cipher 对象的 FingerprintManagerCompat.CryptoObject 对象,用于加解密
        flags, // 可选 flag,建议为 0
        cancellationSignal, // CancellationSignal 对象,用于取消指纹认证,可空,但不建议为 null
        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 // 回调所在 Handler,一般为 null
    )
}

这样,我们的认证功能就完成了,而在Android 9.0新增的BiometricPrompt API中已经提供相关提示弹窗,所以不需要我们自己手动实现弹窗。

/** [Build.VERSION_CODES.Q] 以上拉起指纹认证 */
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, // 包装了 Cipher 对象的 BiometricPrompt.CryptoObject 对象,用于加解密
        cancellationSignal, // CancellationSignal 对象,用于取消指纹认证,不能为空
        executor, // 回调 Executor,不能为空,可使用 activity.mainExecutor
        callback // 认证回调
    )
}

那么crypto怎么获取呢?这个就要结合业务场景来说了,因为Cipher对象在用于加密、解密时获取的方式是不同的。

开启指纹登录功能

上文有说到过,指纹登录功能要先提供开启指纹登录来保存登录需要的数据,因为 玩Android没有单独提供相关的API,所以这里我们就使用登录接口来验证密码的正确性;

所以,首先弹窗提示,让用户输入密码,确认后调用登录接口验证密码正确性,确认密码正确后,将密码暂时缓存,拉起指纹认证;

指纹认证流程在上面已经说过了,这里我们重点介绍Cipher对象的获取。

/** 获取 Cipher 对象 */
fun loadCipher(): Cipher {
    val keyStore = KeyStore.getInstance("AndroidKeyStore")
    keyStore.load(null)
    // keyAlias 为密钥别名,可自己定义,加密解密要一致
    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.ENCRYPT_MODE 初始化
       cipher.init(Cipher.ENCRYPT_MODE, key)
    return cipher
}

获取到Cipher对象后,调用指纹认证,不同版本CryptoObject的对象是不同的,直接新建对应对象,将Cipher对象传入即可。

/** 指纹认证回调成功 */
override fun onAuthenticationSucceeded(result: AuthenticationResult?) {
    // 认证成功,获取 Cipher 对象
    val cipher = result?.cryptoObject?.cipher ?: throw RuntimeException("cipher is null!")
    // 使用 cipher 对登录信息进行加密并保存
    val encryptInfo = cipher.doFinal(loginInfo.toByteArray()).toHexString()
    // 保存 encryptInfo 到本地
    // 保存加密向量到本地
    save(encryptInfo)
    save(cipher.iv.toHexString())
}

这样 开启指纹登录就完成了。

需要注意的有三点:

  1. 加密时,Cipher对象使用cipher.init(Cipher.ENCRYPT_MODE, key)进行初始化;

  2. 指纹认证成功后,使用回调返回Cipher对象对数据进行加密;

  3. 指纹认证成功后,要将Cipher对象中的加密向量iv保存起来。

指纹登录功能

通过上面开启了指纹登录之后,我们就可以在登录页进行指纹登录了。

进入登录页后,可以自动拉起 指纹登录 或者用户点击指纹登录后拉起。

/** 获取 Cipher 对象 */
fun loadCipher(): Cipher {
    val keyStore = KeyStore.getInstance("AndroidKeyStore")
    keyStore.load(null)
    // keyAlias 为密钥别名,可自己定义,加密解密要一致
    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.DECRYPT_MODE 和 iv 进行初始化
       cipher.init(Cipher.DECRYPT_MODE, key, iv)
    return cipher
}

和上面开启一样,拉起指纹认证。

/** 指纹认证回调成功 */
override fun onAuthenticationSucceeded(result: AuthenticationResult?) {
    // 认证成功,获取 Cipher 对象
    val cipher = result?.cryptoObject?.cipher ?: throw RuntimeException("cipher is null!")
    // 使用 cipher 对登录信息进行解密
    val logintInfo = cipher.doFinal(get(encryptInfo).toHexByteArray()
    // 使用 loginInfo 进行登录
    login(loginInfo)
}

这样就完成了指纹登录。

需要注意的有两点:

  1. 解密时,Cipher对象使用cipher.init(Cipher.DECRYPT_MODE, key, iv)进行初始化;

  2. 指纹认证成功后,使用回调返回Cipher对象对数据进行解密。

/   总结   /

看完全文,你学会怎么集成指纹登录功能了吗?我们需要牢记的是,指纹登录就是一个类似于记住密码的功能,在这个过程中使用到了指纹认证来对登录信息进行加密解密,使用的都是Cipher对象,而用于加密和解密时Cipher对象的初始化方式有所不同,解密时需要使用到加密时生成的IvParameterSpec加密向量,而由我们初始化出来的Cipher对象是无法直接使用的,需要使用指纹认证处理之后才能用于加密解密。

想要我的源码吗?想要的话可以全部给你,去找吧!我把所有源码都放在那里!

https://github.com/WangJie0822/SampleProject

推荐阅读:

我的新书,《第一行代码 第3版》已出版!

一看就会!协程原来是这样啊~

Hi~重新认识一下Drawable!

欢迎关注我的公众号

学习技术或投稿

长按上图,识别图中二维码即可关注

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值