首页 安卓& Kotlin Tutorials

Android生物识别API:入门

了解如何通过使用Android BieMomic API来创建安全存储消息的应用程序来实现Android应用程序中的生物识别身份验证。

5/5 1评级

版本

  • Kotlin 1.4,Android 10.0,Android Studio 4.1

除了打电话或发短信之外,智能手机最常用的功能是什么?它拍照吗?文件共享?听音乐或看视频?实际上,它是一个特征,许多人每天使用无数次的次数而无需思考它:生物识别认证!

生物识别身份验证允许您使用指纹或面部快速解锁您的设备,确认它真的 谁使用该设备。在本教程中,您将学习如何使用它来创建名为的应用程序 icrypt. 使用 安卓Biometric API.

此应用程序将安全地存储您的消息,因此只有您可以使用生物识别签名解锁它们。你会学习如何:

  1. 整合 Androidx生物识别库.
  2. 通过a验证用户 生物识别提示.
  3. 使用生物识别凭据加密和解密敏感信息。
  4. 正确处理成功或失败的身份验证。
笔记:本教程需要一个具有生物识别身份验证功能的设备 指纹 或者 安卓face recognition。这里的示例和示例代码将专注于 指纹 身份验证过程。

入门

使用该材料下载 下载材料 在本教程的顶部或底部的按钮。提取并打开Android Studio 4.0或更高版本的入门项目。

在构建和运行之前,您需要包含一个依赖 Androidx生物识别库,这是Android开发人员的一站式用户身份验证解决方案。

打开应用程序级别 build.gradle. file and add the following line to the end of dependencies {...}:

def biometricLibraryVersion = "1.0.1"
implementation "和roidx.biometric:biometric:$biometricLibraryVersion"

在此代码中,您可以指定要使用的库的版本,并将其包含作为编译的依赖项。点击 立即同步 在Android Studio的右上角同步您的项目,您都可以设置!构建和运行,您将看到登录屏幕:

icrypt.登录屏幕

在继续之前,请仔细查看您刚添加到您的代码的库。

介绍这一点

Androidx生物识别库 允许开发人员:

  • 检查设备是否支持生物识别身份验证。
  • 显示标准化的生物识别提示,用于指纹或面部识别。
  • 使用简单的回调检测成功或失败的身份验证尝试。
  • 提供使用设备的PIN /模式/密码而不是生物识别凭据的选项。

这是它在带有不同的Android版本和供应商的引擎盖下工作方式:

Android生物识别架构图表

笔记:如果您有兴趣了解有关Android生物识别架构的更多信息,请退房 官方文件.

您将首先检查设备是否可以使用生物识别身份验证。

检查设备功能

由于Android生物识别API相对较新,并非所有设备都具有生物识别能力。因此,您的第一步是检查您的用户的设备是否能够进行生物识别身份验证。这只需要一些简单的步骤。

创建一个新的 目的 文件命名 生物霉咀 在 - 的里面 利用者 包裹。在Android Studio中,选择 利用者 包,然后点击 文件▸新▸kotlin文件/类。接下来,选择 目的 并输入名称。这将是您管理生物识别认证过程的辅助类。

现在,添加此功能以检查用户的硬件功能:

fun hasBiometricCapability(context: Context): Int {
  val biometricManager = BiometricManager.from(context)
  return biometricManager.canAuthenticate()
}

接下来,在文件顶部添加以下导入:

import android.content.Context
import androidx.biometric.BiometricManager

上面的代码创造了一个 BietricManager. from the app context and calls canAuthenticate() to check whether the hardware is capable of biometric authentication.

但是,这并不能保证用户已准备好使用生物识别身份验证。它们可能有必要的硬件用于面部识别或读取设备上的指纹,但您只能呼叫 生物统计学备注 如果您在设备中注册了指纹或面部 安全设定.

When you run canAuthenticate(), it returns one of three different results:

  1. biometric_success.:设备已准备好使用生物识别提示,这意味着硬件可用,用户已注册其生物识别数据。
  2. biometric_error_none_enrolled.:该设备支持生物识别性,但用户还没有注册其指纹或其脸部。
  3. biometric_error_no_hardware:设备硬件没有生物识别功能。

要确保设备已准备好使用生物识别提示,请添加以下功能:

fun isBiometricReady(context: Context) =
      hasBiometricCapability(context) == BiometricManager.BIOMETRIC_SUCCESS

This returns true only if the device has biometric hardware capability 用户已注册其生物识别性。

实现生物识别登录

You want to show an option for the user to log in with biometrics when they are ready, so open LoginActivity 在 - 的里面 UI. package and replace showBiometricLoginOption() with the code below:

fun showBiometricLoginOption() {
  buttonBiometricsLogin.visibility =
      if (BiometricUtil.isBiometricReady(this)) View.VISIBLE
      else View.GONE
}

这在顶部的“导入”部分中添加了以下内容:

import com.raywenderlich.icrypt.util.BiometricUtil
import kotlinx.android.synthetic.main.activity_login.*

上面的功能是不言自明的:如果设备支持生物识别登录,则显示要使用生物识别器登录的按钮,并且用户设置了指纹或面部ID。否则,它会隐藏按钮,用户无法访问生物识别登录功能。

再次运行应用程序。如果您注册了生物识别性,您会看到一个新的按钮, 使用Biometrics登录,出现在原始登录按钮下方。

登录屏幕带生物识别登录按钮

建筑Bietricprompt.

如果您渴望使用Biometrics登录并消除键入密码的需要,那就有了好消息。您只有两个步骤才能显示生物识别提示以简化您的登录过程。

这是你需要先做的事情:

  1. 提示纽带 使用您所需的消息和配置。
  2. 使用调用活动和回调处理程序初始化生物识别提示。

准备提示

打开 biometricutil.kt. 再次追加此功能:

setBiometricPromptInfo(
    title: String,
    subtitle: String,
    description: String,
    allowDeviceCredential: Boolean
): BiometricPrompt.PromptInfo {
  val builder = BiometricPrompt.PromptInfo.Builder()
      .setTitle(title)
      .setSubtitle(subtitle)
      .setDescription(description)

  // Use Device Credentials if allowed, otherwise show Cancel Button
  builder.apply {
    if (allowDeviceCredential) setDeviceCredentialAllowed(true)
    else setNegativeButtonText("取消")
  }

  return builder.build()
}

接下来,在您刚添加的代码上方包括此导入:

import androidx.biometric.BiometricPrompt

The code above uses a builder class, 生物统计学备注.PromptInfo.Builder, to create the dialog and populate it with the title, subtitle and description — pretty simple!

Are you wondering what allowDeviceCredential is doing here? It allows you to configure fallback options to skip biometric authentication. For example, this lets you offer the option to use the device’s existing passcode/pattern or show a 取消 button when the biometric prompt displays. 生物统计学备注.PromptInfo.Builder offers you both options with some built-in functions.

放ting allowDeviceCredential to true applies setDeviceCredentialAllowed(true) to the dialog builder to create a special button that launches your device’s PIN, passcode or pattern lock screen as an alternative method of user authentication.

The default value for allowDeviceCredential is false. In that case, the biometric prompt shows a 消极的 或者 取消 button. setNegativeButtonText("取消") just sets the button text to 取消。你可以自由地设置任何文本,例如,“离开”。 :]

初始化Bietricprompt.

Next, you’ll initialize the biometric prompt and handle the callbacks with a listener from the calling activity. initBiometricPrompt() does the job. Add the following code to biometricutil.kt.:

fun initBiometricPrompt(
    activity: AppCompatActivity,
    listener: BiometricAuthListener
): BiometricPrompt {
  // 1
  val executor = ContextCompat.getMainExecutor(activity)

  // 2
  val callback = object : BiometricPrompt.AuthenticationCallback() {
    override fun onAuthenticationError(errorCode: Int, errString: CharSequence) {
      super.onAuthenticationError(errorCode, errString)
      listener.onBiometricAuthenticationError(errorCode, errString.toString())
    }

    override fun onAuthenticationFailed() {
      super.onAuthenticationFailed()
      Log.w(this.javaClass.simpleName, "Authentication failed for an unknown reason")
    }

    override fun onAuthenticationSucceeded(result: BiometricPrompt.AuthenticationResult) {
      super.onAuthenticationSucceeded(result)
      listener.onBiometricAuthenticationSuccess(result)
    }
  }

  // 3
  return BiometricPrompt(activity, executor, callback)
}

现在,您需要将以下导入添加到顶部的导入部分:

import android.util.Log
import androidx.core.content.ContextCompat
import androidx.appcompat.app.AppCompatActivity
import com.raywenderlich.icrypt.common.BiometricAuthListener

上面的功能是三件事:

  1. It creates an executor to handle the callback events.
  2. It creates the callback 目的 to receive authentication events on 成功, 失败的 或者 错误 状态使用适当的结果或错误消息。
  3. Finally, it constructs a biometric prompt using the activity, executorcallback references. These three parameters are passed on to the UI level to display the prompt and to handle success or failed authentication.

显示生物识别生物识别

现在,您需要执行上面的步骤以显示生物识别提示。添加一个功能 biometricutil.kt. 将它们整合在一起:

fun showBiometricPrompt(
    title: String = "Biometric Authentication",
    subtitle: String = "Enter biometric credentials to proceed.",
    description: String = "Input your Fingerprint or FaceID to ensure it's you!",
    activity: AppCompatActivity,
    listener: BiometricAuthListener,
    cryptoObject: BiometricPrompt.CryptoObject? = null,
    allowDeviceCredential: Boolean = false
) {
  // 1
  val promptInfo = setBiometricPromptInfo(
      title,
      subtitle,
      description,
      allowDeviceCredential
  )

  // 2
  val biometricPrompt = initBiometricPrompt(activity, listener)

  // 3
  biometricPrompt.apply {
    if (cryptoObject == null) authenticate(promptInfo)
    else authenticate(promptInfo, cryptoObject)
  }
}

The first two statements in this function are obvious — they’re just performing setBiometricPromptInfo()initBiometricPrompt() with the supplied parameters, as mentioned earlier. 提示纽带 will use parameter defaults for title, subtitle and description if you don’t pass anything explicitly.

However, the third statement is a bit cryptic. The biometric prompt uses CryptoObject., if available, along with 提示纽带 to authenticate.

But what’s CryptoObject.?

在跳入这一点之前,看看 生物统计学备注. Simply replace onClickBiometrics() in logeActivity.kt. with the code below:

fun onClickBiometrics(view: View) {
  BiometricUtil.showBiometricPrompt(
      activity = this,
      listener = this,
      cryptoObject = null,
      allowDeviceCredential = true
  )
}

Here, you call showBiometricPrompt() when the user taps 使用Biometrics登录.

现在,运行应用程序并使用Biometrics登录。你会看到这样的东西:

生物识别认证提示

笔记:标准生物识别提示UI根据您的设备而异。

创建加密

现在,生物识别提示已准备就绪,您的下一个目标是利用它来加密和解密您的秘密。这是在哪里 CryptoObject. comes into play!

加密101:密码,密钥库和秘密

CryptoObject. 只是A. 密码,有助于数据加密和解密的对象。密码知道如何使用a 密钥 加密数据。拥有秘密的任何人都可以用相同的密码解密任何加密的东西。

Android在一个名为“的安全系统”中保留删除 keystore.。 Android KeyStore的目的是完全保留Android操作系统之外的关键材料,在已知的安全位置 可信执行环境(TEE) 或者 保险柜。 Android KeyStore将秘密保留为尽可能受到严格限制的,确保应用程序,Android用户佩运空间甚至Linux内核无法访问它。

使用密钥库

生物统计学备注 不知道如何获得秘密,甚至是密钥库的位置。 BieMometricPrompt只是充当网守,以验证您的真实性作为数据的所有者。然后,它要求来自密码的帮助以获取秘密,使用它来加密或解密,然后返回数据。

考虑密码作为中间人,就像 凯瑟克 从着名的科幻电影,“矩阵”,谁只知道如何打开门 你的 Keystore.

所以,当您在应用程序中进行加密或解密时 生物统计学备注,结束到底会发生什么是:

  • 该应用程序询问用户通过生物识别提示验证自己。
  • 在成功验证后,Android KeyStore会生成密码并使用特定的秘密标记它。
  • 密码对加密进行加密 纯文本 并返回A. 密文初始化向量(iv). Together, they’re known as EncryptedMessage, which you’ll see later in this tutorial.
  • You store EncryptedMessage in local storage using a utility class named PreferenceUtil. You’ll decrypt and display it later.
  • During decryption, you authenticate again through the BiometricPrompt. This ensures you’re using the same cipher and SecretKey when you use your fingerprint or face as a signature to decrypt your EncryptedMessage.
  • 然后,密码使用初始化向量在密文上执行解密,然后将其返回到应用程序中显示。

整体过程如下所示:

Android密钥库通过CryptoObject与Android Userspace进行交互

笔记:Androidx生物识别API支持 密码, 苹果电脑签名 用于加密操作。您将使用密码作为标准。

现在你知道步骤,是时候将它们转换为代码!

生成秘密

创建一个新的 目的 命名为 cryptogiceil. 里面 利用者 并在其内部定义下面的常数:

private const val ANDROID_KEYSTORE = "AndroidKeyStore"
private const val YOUR_SECRET_KEY_NAME = "[email protected]"
private const val KEY_SIZE = 128
private const val ENCRYPTION_BLOCK_MODE = KeyProperties.BLOCK_MODE_GCM
private const val ENCRYPTION_PADDING = KeyProperties.ENCRYPTION_PADDING_NONE
private const val ENCRYPTION_ALGORITHM = KeyProperties.KEY_ALGORITHM_AES

接下来,保持你的光标 关键词 并按 选项返回 在Mac或 Control-Alt-O 在Windows导入类。为您的其他进口做同样的事情。在添加更多函数以进行加密/解密时,您将需要这些常量。

然后,使用Android密钥库生成秘密:

fun getOrCreateSecretKey(keyName: String): SecretKey {
  // 1
  val keyStore = KeyStore.getInstance(ANDROID_KEYSTORE)
  keyStore.load(null) // Keystore must be loaded before it can be accessed
  keyStore.getKey(keyName, null)?.let { return it as SecretKey }

  // 2
  val paramsBuilder = KeyGenParameterSpec.Builder(
      keyName,
      KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT
  )
  paramsBuilder.apply {
    setBlockModes(ENCRYPTION_BLOCK_MODE)
    setEncryptionPaddings(ENCRYPTION_PADDING)
    setKeySize(KEY_SIZE)
    setUserAuthenticationRequired(true)
  }

  // 3
  val keyGenParams = paramsBuilder.build()
  val keyGenerator = KeyGenerator.getInstance(
      KeyProperties.KEY_ALGORITHM_AES,
      ANDROID_KEYSTORE
  )
  keyGenerator.init(keyGenParams)

  return keyGenerator.generateKey()
}

该功能的名称是不言自明的。它执行以下步骤:

  1. keyName,在这个功能中,是你的 别名. It looks for keyName in KeyStore 和 returns the associated SecretKey.
  2. If no SecretKey exists for this keyName, you create paramsBuilder for encryption and decryption, applying the constants you defined earlier, such as ENCRYPTION_BLOCK_MODEKEY_SIZE. In the same block, setUserAuthenticationRequired(true) ensures that the user is only authorized to use the key if they authenticated themselves using the password/PIN/pattern or biometric.
  3. It prepares keyGenerator 使用 configuration from paramsBuilder 和 returns the generated SecretKey.

使用SecretKey生成,您现在可以加密一些秘密!

加密和解密您的秘密

再次构建并运行并使用指纹登录。您将看到一个空屏幕,右下角有一个浮动动作按钮。点按按钮;它会导航到 加密活动,这看起来像这样:

icrypt.消息输入屏幕

尝试输入一些文本并点击 加密消息 在屏幕的底部 - 但没有发生任何事情!您的下一个任务是加密您刚输入的文本并将其存储在某处安全。

将明文加密到密文

You need a cipher to execute encryption and decryption with a SecretKey, remember? Add getCipher() 里面 cryptogalutil.kt.. That gives you a 密码 instance:

fun getCipher(): Cipher {
  val transformation = "$ENCRYPTION_ALGORITHM/$ENCRYPTION_BLOCK_MODE/$ENCRYPTION_PADDING"

  return Cipher.getInstance(transformation)
}

Here, transformation defines the encryption algorithm with additional information, following Java’s 标准密码算法名称 documentation.

Now, prepare the 密码 instance providing the SecretKey you need for encryption:

fun getInitializedCipherForEncryption(): Cipher {
  val cipher = getCipher()
  val secretKey = getOrCreateSecretKey(YOUR_SECRET_KEY_NAME)
  cipher.init(Cipher.ENCRYPT_MODE, secretKey)

  return cipher
}

笔记 that the SecretKey is generated only once — when you use it for the first time. If 密码 requires it later, it’ll use the same SecretKey, executing getOrCreateSecretKey() to unlock your secrets.

You’re now ready to encrypt and hide your secrets! Add this convenient function, right after getInitializedCipherForEncryption():

fun encryptData(plaintext: String, cipher: Cipher): EncryptedMessage {
  val ciphertext = cipher
    .doFinal(plaintext.toByteArray(Charset.forName("UTF-8")))
  return EncryptedMessage(ciphertext, cipher.iv)
}

This function converts plaintext to ciphertext. After you pass your plaintext and cipher through this function, the cipher does its magic to encrypt the plaintext, then returns EncryptedMessage.

EncryptedMessage 是一个数据类 常见的 package. It consists of your 密码Text, initializationVector for the cipher and savedAt property, which keeps the timestamp of the moment you created an EncryptedMessage.

加密动作发生在内 加密活动.

打开 encryptionActivity.kt. 并在课堂底部添加此函数:

private fun showBiometricPromptToEncrypt() {
  // 1
  val cryptoObject = BiometricPrompt.CryptoObject(
    CryptographyUtil.getInitializedCipherForEncryption()
  )
  // 2
  BiometricUtil.showBiometricPrompt(
    activity = this,
    listener = this,
    cryptoObject = cryptoObject
  )
}

以上功能执行了两个简单的任务。它:

  1. Creates CryptoObject. for the biometric prompt by calling cryptogiceil..getInitializedCipherForEncryption() from cryptogiceil..
  2. Displays the biometric prompt using showBiometricPrompt(), passing the activity reference, listener, to handle callback actions and CryptoObject. as the cipher.

Next, replace onClickEncryptMessage() with:

fun onClickEncryptMessage(view: View) {
  val message = textInputMessage.editText?.text.toString().trim()
  if (!TextUtils.isEmpty(message)) {
    showBiometricPromptToEncrypt()
  }
}

在输入要加密的消息时,请简单地显示在点击按钮时显示生物识别提示。

通过加密能力,是时候将加密与生物识别身份验证相结合了。

处理回调

Now, the final step — you need to encrypt and save your message, which can only happen if biometric authentication is successful. Find onBiometricAuthenticationSuccess(), which is already implemented in 加密活动 为方便起见。将下面的代码插入该功能:

result.cryptoObject?.cipher?.let {
  val message = textInputMessage.editText?.text.toString().trim()
  if (!TextUtils.isEmpty(message)) {
    encryptAndSave(message, it)
    confirmInput()
  }
}

这将从结果取决于成功回调,使用它来加密您的消息,然后保存。然后它在完成时显示确认警报。

The actual encryption and storage of the message happens inside encryptAndSave(). Create it at the end of encryptionActivity.kt. as follows:

private fun encryptAndSave(plainTextMessage: String, cipher: Cipher) {
  val encryptedMessage = CryptographyUtil.encryptData(plainTextMessage, cipher)

  PreferenceUtil.storeEncryptedMessage(
    applicationContext,
    prefKey = encryptedMessage.savedAt.toString(),
    encryptedMessage = encryptedMessage
  )
}

Here, you’re converting your plainTextMessage to EncryptedMessage with the help of the cipher and storing it in the SharedPreference. with the savedAt timestamp as its 偏好键.

再次构建并运行,导航到 加密活动 并挖掘 加密消息 在输入某些文本后。出现生物识别提示。用指纹和指纹进行身份验证 - 您加密了您的第一条消息!

返回,您将看到加密消息的列表,排序最新的顶部:

icrypt.消息列表

现在,点击列表中的任何秘密消息。这将打开 解密除非您验证自己,否则拒绝显示您的秘密信息。

icrypt.解密消息屏幕

别担心,你会学会如何很快解锁......

解密密文到明文

只有您可以通过与生物识别性进行身份验证,只能看到您的秘密消息,但您需要一个具有正确配置的密码来将密文转换回明文。获得此密码,打开 cryptogalutil.kt. 再次添加以下功能:

fun getInitializedCipherForDecryption(
      initializationVector: ByteArray? = null
  ): Cipher {
  val cipher = getCipher()
  val secretKey = getOrCreateSecretKey(YOUR_SECRET_KEY_NAME)
  cipher.init(
    Cipher.DECRYPT_MODE,
    secretKey,
    GCMParameterSpec(KEY_SIZE, initializationVector)
  )

  return cipher
}

Here, you’re passing initializationVector from EncryptedMessage. Note that you need initializationVector to retrieve the same set of parameters you used for encryption so you can revert the encryption. Then, you initialize the cipher again, this time in 解密 模式与keystore的所需规格和secriteke。

之后,编写一个函数来执行密码解密:

fun decryptData(ciphertext: ByteArray, cipher: Cipher): String {
  val plaintext = cipher.doFinal(ciphertext)
  return String(plaintext, Charset.forName("UTF-8"))
}

The function above is a mirror image of your previously added encryptData(). You’ll pass 密文密码 as arguments here, then the cipher will work its magic again and return a plain string, decrypting the ciphertext.

实施您的构建块

现在你已经建造了积木,现在是时候和他们一起玩了!

和roid字符玩积木

您需要再次提示生物识别身份验证以在攻丝时解锁您的秘密 解密消息.

打开 解密, add showBiometricPromptToDecrypt() at the bottom of the class and call it inside onClickDecryptMessage(). Your code will look like this:

fun onClickDecryptMessage(view: View) {
  showBiometricPromptToDecrypt()
}

private fun showBiometricPromptToDecrypt() {
  encryptedMessage?.initializationVector?.let { it ->
    val cryptoObject = BiometricPrompt.CryptoObject(
       CryptographyUtil.getInitializedCipherForDecryption(it)
    )

    BiometricUtil.showBiometricPrompt(
      activity = this,
      listener = this,
      cryptoObject = cryptoObject
   )
  }
}

showBiometricPromptToDecrypt() looks for the initializationVector from your EncryptedMessage in this class. It then calls getInitializedCipherForDecryption() from cryptogiceil. to prepare a cipher for decryption, providing the specs from initializationVector. CryptoObject. holds the cipher and 生物统计学备注 在这里充当网守 - 只有在成功的身份验证时才获得密码,只有那么你可以解锁你的秘密!

The rest of your task is easy. Insert the code below inside onBiometricAuthenticationSuccess():

result.cryptoObject?.cipher?.let {
  decryptAndDisplay(it)
}

This takes the 密码 from the authentication result 和 uses it to decrypt and display your message.

Now, define decryptAndDisplay():

private fun decryptAndDisplay(cipher: Cipher) {
  encryptedMessage?.cipherText?.let { it ->
    val decryptedMessage = CryptographyUtil.decryptData(it, cipher)
    textViewMessage.text = decryptedMessage
  }
}

The steps are simple here: You’re asking for help from cryptogiceil..decryptData(), providing your ciphertext and the cipher. It performs a decryption operation and returns decryptedMessage in plaintext. decryptedMessage then displays in your textview..

立即构建并运行,并尝试使用生物识别提示解锁您的一个秘密消息。你会惊讶于无痛,但安全的过程是多么痛苦!

最终屏幕在屏幕中心显示您的秘密信息,如下所示:

在屏幕上解密消息

恭喜,欢迎来到无密码认证!

庆祝与五彩纸屑的Android

然后去哪儿?

点击查看最终项目 下载材料 本教程顶部或底部的按钮。

不要在这里结束生物识别和加密之旅;通过这些有用的资源增强您的知识:

我希望您喜欢创建密码应用程序和了解生物认证过程。如果您有任何疑问或意见,请加入下面的论坛讨论。

平均评级

5/5

为此内容添加评级

1 rating

更像这样的

贡献者

评论