Android指纹从入门到"放弃"
一、背景
Android在23(Android M)上新增了对指纹识别的硬件支持,应用可以通过调用系统Api实现指纹验证相关功能,相对于传统的手势,密码等验证方式,指纹验证安全性更高,速度也更快。
二、业界方案
1.1、FIDO
FIDO是CFCA提出的一种生物认证标准,通过FIDO sdk、手机硬件、FIDO后台构成了一套指纹验证解决方案,保证指纹验证流程不被篡改,该方案通过与手机厂商合作,客户端侧对秘钥的操作均在TEE环境中,安全性高。FIDO方案不仅需要客户端集成sdk,应用后台也需要植入FIDO后台sdk,业务流程需要由FIDO客户端sdk、应用客户端、应用后台、FIDO后台sdk、FIDO服务端五方共同完成,较为复杂。一般针对指纹支付等安全性级别较高的场景。
1.2、TENCENT SOTER
TENCENT SOTER是一种生物认证标准,同时也是腾讯生物认证平台。TENCENT SOTER主要着眼于如何安全、高效并简单地使用你设备上的传感器进行鉴权——最重要,也是目前用到最多的就是指纹传感器。TENCENT SOTER与FIDO一样,会通过与手机厂商的合作,客户端侧相关秘钥操作均在TEE环境中。TENCENT SOTER采用三级秘钥方案,应用方后台不需要接入sdk,接入相对简单。一般针对指纹支付等安全性级别较高的场景。
1.3、Android Framework
Android系统提供的相关生物识别验证接口,应用可以通过对这些接口的调用实现Android指纹验证功能,但安全性较低,容易被攻击者绕过,若需要使用在支付等安全性级别较高的场景,需要配合额外的安全保护逻辑),且存在较多的兼容性问题。
三、Android原生方案
3.1、Api介绍
- Android 在23(Android M)上新增了指纹识别Api:FingerprintManager,第三方app可以通过对该类的开发与使用,实现指纹相关功能。开发者通过该Api打开指纹认证流程时,系统仅会打开设备的指纹模块监听,并不会有UI相关展示,需要开发者根据自身App要求弹出对应的交互流程。
- Android在28(Android P)上新增了生物识别Api:BiometricPrompt,并推荐开发者使用最新的生物识别Api替换原来的FingerprintManager,该Api不仅只针对指纹,而是囊括了指纹、人脸、虹膜等生物特征识别,但现阶段只开放了指纹相关功能。开发者使用该Api进行指纹认证流程时,系统在会打开设备的指纹模块监听的同时,还会弹出一个系统级的Dialog提示用户正在进行指纹解锁流程。
FingerprintManager与BiometricPrompt中,指纹验证涉及的调用接口十分类似,本文就以BiometricPrompt为例
- 判断当前设备是否可以进行生物识别
BiometricManager : canAuthenticate () (29)
- 设置弹窗系统弹窗title
BiometricPrompt.Builder(28): setSubtitle (CharSequence subtitle)
- 设置系统弹窗的取消按钮
BiometricPrompt.Builder(28): setNegativeButton (CharSequence text, Executor executor,DialogInterface.OnClickListener listener)
- 开启指纹认证流程,开发者调用该Api后,生物识别传感器将打开,同时屏幕会弹出一个系统级Dialog提示用户正在进行生物识别。
BiometricPrompt (28): authenticate (BiometricPrompt.CryptoObject crypto, CancellationSignal cancel, Executor executor, BiometricPrompt.AuthenticationCallback callback)
- crypto 成功进行生物验证后将使用该对象验证本次流程的可靠性
- cancel 可以用于结束本次验证流程的对象
- executor 本次验证流程的执行者(线程相关)
- callback 用于接收本次验证流程结果的回调
- 指纹验证流程出现不可重试的错误时(如验证5次不通过),系统会中止指纹验证流程,并在一定时间内不可再调起。
BiometricPrompt.AuthenticationCallback(28): onAuthenticationError(int errorCode, CharSequence errString)
- errorCode 错误码
- errString 错误信息
- 系统识别出指纹,但该指纹未匹配指纹库中指纹
BiometricPrompt.AuthenticationCallback(28): onAuthenticationFailed()
- 指纹验证流程出现可重试的错误时(如手指滑动过快)
BiometricPrompt.AuthenticationCallback(28): onAuthenticationHelp(int helpCode, CharSequence helpString)
- helpCode 错误码
- helpString 错误信息
- 指纹验证成功时调用
BiometricPrompt.AuthenticationCallback(28): onAuthenticationSucceeded(BiometricPrompt.AuthenticationResult result)
- result 包含authenticate方法中的crypto等对象
3.2、版本兼容
- Api23-27:所有功能实现都使用FingerprintManager,弹窗由开发者自定义,非系统弹窗
- Api28:判断当前设备是否可以进行指纹识别,需要使用FingerprintManager;进行指纹验证流程,需要使用BiometricPrompt
- Api29+:判断当前设备是否可以进行指纹识别,需要使用BiometricManager;进行指纹验证流程,需要使用BiometricPrompt
- 也可以使用androidx.biometric包,在大于Api23的所有的Android版本都使用同一套接口,但需要解决将support包全部转为androidx包等一系列问题。
3.3、存在问题
3.3.1、UI兼容性问题(针对BiometricPrompt)
由于FingerprintManager是开发方自定义UI,因此不存在UI兼容性问题。BiometricPrompt使用的是系统弹窗,开发者不可以自定义弹窗样式,仅可以设置弹窗上的相关文案及按钮点击事件。因此不同厂商对系统Dialog样式的不同实现,会导致指纹验证流程中出现不同的交互。
华为mate20 Android29(屏下指纹)
小米Mix2s Android28
三星Galaxy S8+ Android28
OPPO R15X Android28
3.3.2、功能性问题
- 1、在OPPO R15X上(Android28),BiometricDialog弹出后,点击取消按钮的点击事件没有分发给第三方App。
- 2、原生系统,当BiometricDialog弹出后,按下Menu/HOME键,指纹验证申请已经取消,但是弹框BiometricDialog无法消失。
- 3、原生系统,若操作不当,存在在锁屏状态下弹出Dialog导致无法解锁,用户无法进入Launcher进行UI交互的风险。出现这种“意外” 的情况是,比如:UI处在进入申请BiometricPrompt生物识别弹框上下文,由于网络/apk性能等种种原因,并未立马申请弹出Dialog,突然遇到Power熄屏,或者亮屏超时,立马按下Power点亮屏幕。此时三方应用处在onPaused状态,但是异步发起生物识别申请,导致"意外"发生。
3.3.3、安全性问题
上文介绍的指纹验证Api:public void authenticate (BiometricPrompt.CryptoObject crypto, CancellationSignal cancel, Executor executor, BiometricPrompt.AuthenticationCallback callback)
,若对第一个crypto参数使用方式不正确,则存在指纹验证被绕过的风险
流程
第一步:调用authenticate方法,弹出UI提示用户输入指纹-->第二步:用户验证指纹,用户验证指纹成功-->第三步:调用AuthenticationCallback.onAuthenticationSucceeded方法,执行解锁后逻辑
漏洞
- root手机
- 通过hook重写authenticate方法,在authenticate方法中直接调用AuthenticationCallback.onAuthenticationSucceeded方法。若应用未正确使用crypto参数,则此方案绕过第二步验证指纹。
3.4 解决方案
3.4.1、UI兼容性问题
若使用BiometricPrompt则具体UI无法由第三方开发者指定,若要统一UI,建议使用FingerprintManager
3.4.2、功能性问题
若使用BiometricPrompt,则弹窗为系统级弹窗,其展示与消失逻辑无法由
第三方开发者指定,第三方开发者唯一可以控制的就是保证调起指纹识别流程(即调用authenticate方法)的时机正确。
3.4.3、安全性问题
方案一
流程:
- 在调用authicate方法前,通过AndroidKeyStore生成一把对称秘钥,加密串A得到串B,并用这把秘钥生成crypto对象传入authicate方法。
- 在AuthenticationCallback.onAuthenticationSucceeded方法中取出crypto对象,并解密串B得到C,比较A与C,若相等则通过。
- 若跳过验证流程直接调用AuthenticationCallback.onAuthenticationSucceeded,则比较A与C会失败。
问题与总结:
- 攻击者若在流程中劫持到秘钥,并生成crypto对象传入,则仍可以绕过
- 兼容性问题小,对安全级别要求低的场景推荐使用
方案二
流程:
- 在调用authicate方法前,通过AndroidKeyStore生成一对非对称秘钥,设置要访问这把秘钥对必须在生物识别成功之后,利用公钥加密串A得到串B,生成crypto对象传入authicate方法。
- 在AuthenticationCallback.onAuthenticationSucceeded方法中取出crypto对象,利用私钥解密串B得到C,比较A与C,若相等则通过。
- 若跳过验证流程直接调用AuthenticationCallback.onAuthenticationSucceeded,则在访问私钥解密数据时,系统会抛出
android.security.keystore.KeyPermanentlyInvalidatedException: Key permanently invalidated
,无法继续执行。
问题与总结:
- 该方案相对方案一更加安全,通过AndroidKeystore保证指纹验证流程的不被绕过
- 由于Android碎片化问题,该方案在使用那把非对称秘钥时,有非常多的兼容性问题
四、总结
上文详细介绍了Android系统提供的指纹接口的使用方式以及使用过程中存在的问题,对比FIDO及TENCENT SOTER方案,我们可以得出以下图表
比较项 | TENCENT SOTER | Android Framework | FIDO |
---|---|---|---|
接入成本 | 较低 | 低 | 高 |
是否需要联网 | 是 | 依赖方案 | 是 |
兼容性问题 | 少 | 多 | 少 |
用户隐私保护 | 好(不会获取指纹图案) | 好(不会获取指纹图案) | 好(不会获取指纹图案) |
安全性 | 高 | 依赖方案 | 高 |
综合以上,云闪付在对安全性要求相对较低的场景(如解锁),使用Android原生指纹接口配合AndroidKeystore实现,无需联网,速度快,兼容性问题少;在对安全性要求较高的场景(如支付),选择采用第三方FIDO解决方案,兼容性问题少,安全性高。