头图

为了保护隐私和敏感数据,应用往往会增加用户登录功能。如果您的应用使用了传统的登录方式,那么它的授权过程可能类似如图 1 中所示: 用户输入用户名和密码,应用会根据输入的数据生成设备凭据,然后将其发送到远端服务器进行验证,通过验证后会返回给应用一个 userToken,随后应用便可使用该 token 去服务器查询受限的用户数据。无论是要求用户每次打开应用都需要登录,还是只要求在安装启动后进行仅此一次的登录,图 1 所示的流程都适用。

△ 图 1: 未使用生物识别的授权流程

△ 图 1: 未使用生物识别的授权流程

然而,图 1 这种授权方式有一些弊端:

  • 如果对于每次独立的会话都需要进行验证 (比如银行类的应用),那么这套流程会让用户感到非常繁琐,因为每次打开应用都需要输入一遍密码;
  • 如果验证发生在应用首次安装后打开时 (比如邮件类应用),那么拥有该设备的任何人都可以查看设备所有者的隐私内容,因为应用无法验证当前使用者是否为设备所有者本人。

为了弥补这些弊端,我们引入了生物识别身份验证的方式,为终端用户的身份验证流程提供了诸多便利。不仅如此,这套技术对开发者也更具吸引力,即使业务逻辑可能不需要用户频繁登录。使用生物识别身份验证带来的最关键的好处在于,整个认证过程十分简短,只需要轻按一下传感器或是看一眼设备就完成了。而作为开发者,您要确定您的用户必须要进行重新认证的频率,是一天一次,一周一次还是每次打开应用都需要重新认证。总而言之,我们提供的 API 封装了许多功能,使开发者及其用户获得更加友好方便的登录体验。

如今,许多处理个人数据的应用 (例如邮件或社交应用) 在安装后往往只需要进行一次性身份验证。这种做法普及起来,是因为每次打开应用都需要输入用户名和密码的方式对用户体验造成了不良影响。但若是使用了生物识别技术,用户便不再担心安全性的缺失。即使您的应用还是使用一次性的身份验证,也可以考虑定期进行生物特征识别,以验证是否为同一用户。验证周期的长短完全取决于开发者的设定。

  • 如果应用要求每次独立会话都需要进行验证 (或者是某些较为频繁的认证频率,例如每 2 小时一次或者每天一次等等),那么相比每次都手动输入密码进行验证的话,看一眼设备或轻按一下传感器这种方式就只是一种微不足道的操作。
  • 如果应用仅需在安装后进行一次性验证 (例如邮件类应用),那么添加生物识别功能的代价只是让用户多了一个拿起设备然后看一眼的操作,但却额外提供了更加安全的保障。
  • 如果用户希望无需额外进行验证,仍能够保持邮件的打开状态,那么应该提供选项允许这种行为。

对于想获得更多隐私保护的用户,生物识别功能能够提供额外的安心保障。无论哪种方式,同增加的收益相比,用户所付出的成本微乎其微。

使用 BiometricPrompt API 实现生物识别功能

通过 BiometricPrompt API,您可以在加密和不加密的情况下实现身份验证。如果您的应用需要更强安全性的保障 (例如医疗类或银行类应用),则可能需要 将加密密钥同生物特征绑定在一起 来验证用户的身份。否则您仅需向用户提供生物识别身份验证即可。两种方式的代码实现很类似,除了在需要加密时要用到 CryptoObject 实例。

加密版本:

biometricPrompt.authenticate(promptInfo, BiometricPrompt.CryptoObject(cipher))

在上述代码中,我们向 CryptoObject 传递了 Cipher 参数,除此之外也支持其余加密对象,比如使用 MacSignature

不使用 CryptoObject 的版本:

biometricPrompt.authenticate(promptInfo)

若要在 Android 应用中实现生物识别身份验证,请使用 AndroidX Biometric 代码库。虽然 API 可以自动处理不同的认证级别 (指纹、面部识别、虹膜识别等),但您仍然可以通过 setAllowedAuthenticators() 方法设置应用可以接受的生物认证级别,具体如下面的代码所示。Class 3 (以前被称为 Strong) 级别代表您希望使用生物识别来解锁存储在 Keystore 中的凭证;Class 2 (以前被称为 Weak) 级别代表您只需要使用生物识别来解锁应用,而不依赖于加密技术保护的凭证进一步进行身份验证。还有一个 Class 1 级别,但此级别在应用中并不可用。更多详情,请查看 Android 兼容性定义文档

fun createPromptInfo(activity: AppCompatActivity): BiometricPrompt.PromptInfo =

   BiometricPrompt.PromptInfo.Builder().apply {

      setAllowedAuthenticators(BIOMETRIC_STRONG)
     //  继续设置其他 PromptInfo 属性,如标题、副标题、描述等。
}.build()

加密、auth-per-use (每次验证) 密钥 vs time-bound (时间限制) 密钥

auth-per-use 密钥 是一种被用来执行一次性加密操作的密钥。举个例子,如果您想执行 10 次加密操作,那么就必须解锁 10 次密钥。因此,auth-per-use 就意味着每次使用密钥时,都必须进行认证 (即解锁密钥)。

time-bound 密钥 则是一种在一定的时间段内有效的密钥,您通过向 setUserAuthenticationValidityDurationSeconds 方法传递一个以秒为单位的时间参数,过了该时间后该密钥就需要再次进行解锁。如果您传递的时间参数值为 -1,也就是默认值,那么系统会认为您想要使用 auth-per-use 密钥。在这里若您不想设置为 -1,那么我们建议您至少设置为 3 秒,这样系统会遵循您所设置的时间。想要了解更多创建 time-bound 密钥的方法,请参考 Jetpack Security 中关于 MasterKeys 的内容。

通常,即前面提到的 -1 的情况时,您通过向 BiometricPrompt.authenticate() 方法传递一个 CryptoObject 参数来请求 auth-per-use 密钥。然而,您也可以不使用 CryptoObject,而是设置一个很短的时间参数 (比如 5 秒),来将 time-bound 密钥当作 auth-per-use 密钥来使用。这两种方法对于验证用户身份来说实际上是等同的,如何选择取决于您设计应用交互的方式。

让我们看看这两种不同类型的密钥是如何工作的: 当您使用 CryptoObject 时,只有某个特定操作才能够解锁密钥。这是因为 Keymint (或者是 Keymaster) 获取了一个带有特定 operationId 的 HardwareAuthToken (HAT)。当密钥被解锁后,您只能使用密钥去执行那些被定义为 Cipher/Mac/Signature 的操作,并只能执行一次,因为这是一个 auth-per-use 密钥。若不使用 CryptoObject,那么被发送到 Keymint 的 HAT 就没有 operationId,此时,Keymint 会去查找一个带有有效时间戳 (时间戳 + 密钥使用期限 > 当前时间) 的 HAT,在有效时间内,您都能够使用该密钥,因为它是一个 time-bound 密钥。

这样看上去,似乎只要在有效的时间窗口内,任何应用都可以使用 time-bound 密钥。但实际上,只要不是用户空间 (user-space) 受到损害,不用担心某个 X 应用使用了某 Y 应用的密钥或操作。Android 框架不会允许其他应用获取或者初始化另一个应用的操作。

总结

在本篇文章中,我们介绍了:

  • 只有用户名 + 密码的认证方式存在问题的原因;
  • 在应用中选择使用生物识别身份验证的原因;
  • 不同类型应用在设计认证方式时的注意事项;
  • 如何在启用或未启用加密的情况下调用 BiometricPrompt
  • auth-per-usetime-bound 两种加密密钥的不同。

在下一篇文章中,我们将为您带来如何合理地将生物识别身份验证的流程整合到应用的 UI 和业务逻辑中。敬请关注!


Android开发者
404 声望2k 粉丝

Android 最新开发技术更新,包括 Kotlin、Android Studio、Jetpack 和 Android 最新系统技术特性分享。更多内容,请关注 官方 Android 开发者文档。