您必须手动输入用户名和密码才能登录某些 Android 应用程序的日子已经一去不复返了。不仅您必须手动输入,而且这也是一个耗时的过程。此外,如果您忘记了密码或用户名,则必须通过一系列步骤来恢复它。但另一方面,如果我们使用指纹进行身份验证,则无需记住密码。此外,没有两个人可以拥有相同的指纹,所以我们不必担心真实性。
因此,在这篇文章中,我们将学习如何在我们的 Android 应用程序中使用指纹认证。那么,让我们开始吧。
指纹认证概述
随着 Android 6.0 (Android M) 的发布,API 发生了大量变化,其中之一就是指纹认证。现在,我们可以在具有指纹传感器的设备中轻松地在我们的应用程序中实现指纹认证。指纹认证的整个过程可以概括为以下几个步骤:
- 在项目的清单文件中请求指纹认证权限。
- 由于指纹只能在其锁屏受 PIN、图案或密码保护的设备上注册。因此,我们必须检查设备的锁定屏幕是否受 PIN、图案或密码保护。
- 然后,创建 FingerprintManager 类的实例。
- 您必须访问用于在 Android 设备上存储加密密钥的存储区域,即 Keystore。因此,创建一个 Keystore 实例以获取对 Android Keystore 容器的访问权限。之后,借助 keyGenerator 类生成加密密钥并将其存储在 Keystore 容器中。
- 借助生成并存储在 Keystore 容器中的密钥,初始化 Cipher 类的实例,并使用该实例创建一个 CryptoObject 并将其分配给您之前创建的 FringerprintManager 实例。
- 调用 FingerprintManger 类的 authenticate 方法并实现处理回调的方法。
指纹认证实现
所以,我们已经看到了指纹认证的理论。现在让我们继续执行相同的部分。
在 Android Studio 中创建一个新项目并根据您的选择命名。此外,将最低 API 设置为 23,即 Android 6.0。
创建项目后,请确保您的设备具有指纹以外的某种身份验证,因为指纹身份验证仅在这种情况下有效。
在你的androidmanifest.xml文件中添加指纹的权限。因此,在清单文件中添加 USE_FINGERPRINT 权限:
<uses-permission android:name="android.permission.USE_FINGERPRINT"/>
在进入应用程序的编码部分之前,让我们为 UI 部分编写代码。在 UI 中,我们将拥有一个 ImageView 和一个 TextView。因此,activity_main.xml文件的代码是:
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<ImageView
android:layout_width="160dp"
android:layout_height="160dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
android:layout_marginStart="8dp"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_marginTop="8dp"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginBottom="8dp"
android:src="@drawable/ic_fingerprint"
android:layout_marginEnd="8dp"
android:id="@+id/fingerprint_iv"/>
<TextView
android:id="@+id/fingerprint_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
android:layout_marginTop="32dp"
app:layout_constraintTop_toBottomOf="@+id/fingerprint_iv"
android:layout_marginEnd="8dp"
android:text="Touch the Fingerpeint Sensor"
android:textSize="24sp"/>
</androidx.constraintlayout.widget.ConstraintLayout>
您可以根据自己的选择替换 ImageView 的图像。
指纹认证使用 KeyguardManager 和 FingerprintManager。所以,在 onCreate() 函数中,需要获取这两个服务:
class MainActivity : AppCompatActivity() {
private lateinit var fingerprintManager: FingerprintManager
private lateinit var keyguardManager: KeyguardManager
override fun onCreate(savedInstanceState: Bundle) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
if (checkLockScreen()) {
//some other task
}
}
private fun checkLockScreen(): Boolean {
keyguardManager = getSystemService(Context.KEYGUARD_SERVICE)
as KeyguardManager
fingerprintManager = getSystemService(Context.FINGERPRINT_SERVICE)
as FingerprintManager
//some other task
}
}
我们的下一个任务是检查锁定屏幕是否受 PIN 或密码保护。此外,如果它受密码保护,那么我们会检查某些指纹是否已经与设备相关联。因此,我们将在MainActivity.kt文件的**checkLockScreen()**方法中执行这些检查。
private fun checkLockScreen(): Boolean {
keyguardManager = getSystemService(Context.KEYGUARD_SERVICE)
as KeyguardManager
fingerprintManager = getSystemService(Context.FINGERPRINT_SERVICE)
as FingerprintManager
if (keyguardManager.isKeyguardSecure == false) {
Toast.makeText(this,
"Lock screen security not enabled",
Toast.LENGTH_LONG).show()
return false
}
if (ActivityCompat.checkSelfPermission(this,
Manifest.permission.USE_FINGERPRINT) !=
PackageManager.PERMISSION_GRANTED) {
Toast.makeText(this,
"Permission not enabled (Fingerprint)",
Toast.LENGTH_LONG).show()
return false
}
if (fingerprintManager.hasEnrolledFingerprints() == false) {
Toast.makeText(this,
"No fingerprint registered, please register",
Toast.LENGTH_LONG).show()
return false
}
return true
}
现在,我们必须生成一个加密密钥,该密钥将存储在 Android 密钥库系统中。因此,我们必须获得 Keystore 的访问权限,然后在generateKey()方法的帮助下生成加密密钥。
class MainActivity : AppCompatActivity() {
...
private lateinit var keyStore: KeyStore
private lateinit var keyGenerator: KeyGenerator
private val KEY_NAME = "my_key"
...
override fun onCreate(savedInstanceState: Bundle) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
if (checkLockScreen()) {
generateKey()
//some code
}
}
private fun checkLockScreen(): Boolean {
//some code
}
private fun generateKey() {
try {
keyStore = KeyStore.getInstance("AndroidKeyStore")
} catch (e: Exception) {
e.printStackTrace()
}
try {
keyGenerator = KeyGenerator.getInstance(
KeyProperties.KEY_ALGORITHM_AES,
"AndroidKeyStore")
} catch (e: NoSuchAlgorithmException) {
throw RuntimeException(
"Failed to get KeyGenerator instance", e)
} catch (e: NoSuchProviderException) {
throw RuntimeException("Failed to get KeyGenerator instance", e)
}
try {
keyStore.load(null)
keyGenerator.init(
KeyGenParameterSpec.Builder(KEY_NAME,
KeyProperties.PURPOSE_ENCRYPT or KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_CBC)
.setUserAuthenticationRequired(true)
.setEncryptionPaddings(
KeyProperties.ENCRYPTION_PADDING_PKCS7)
.build())
keyGenerator.generateKey()
} catch (e: NoSuchAlgorithmException) {
throw RuntimeException(e)
} catch (e: InvalidAlgorithmParameterException) {
throw RuntimeException(e)
} catch (e: CertificateException) {
throw RuntimeException(e)
} catch (e: IOException) {
throw RuntimeException(e)
}
}
}
所以,到目前为止,我们已经生成了密钥。我们的下一个任务是初始化将用于 CryptoObject 实例的密码。此 CryptoObject 将在指纹认证过程中使用。因此,在MainActivity.kt文件中创建一个名为initCipher()的方法:
class MainActivity : AppCompatActivity() {
...
private lateinit var cipher: Cipher
private lateinit var cryptoObject: FingerprintManager.CryptoObject
override fun onCreate(savedInstanceState: Bundle) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
if (checkLockScreen()) {
generateKey()
if (initCipher()) {
cipher.let {
cryptoObject = FingerprintManager.CryptoObject(it)
}
}
}
}
private fun checkLockScreen(): Boolean {
//some code
}
private fun generateKey() {
//some code
}
private fun initCipher(): Boolean {
try {
cipher = Cipher.getInstance(
KeyProperties.KEY_ALGORITHM_AES + "/"
+ KeyProperties.BLOCK_MODE_CBC + "/"
+ KeyProperties.ENCRYPTION_PADDING_PKCS7)
} catch (e: NoSuchAlgorithmException) {
throw RuntimeException("Failed to get Cipher", e)
} catch (e: NoSuchPaddingException) {
throw RuntimeException("Failed to get Cipher", e)
}
try {
keyStore.load(null)
val key = keyStore.getKey(KEY_NAME, null) as SecretKey
cipher.init(Cipher.ENCRYPT_MODE, key)
return true
} catch (e: KeyPermanentlyInvalidatedException) {
return false
} catch (e: KeyStoreException) {
throw RuntimeException("Failed to init Cipher", e)
} catch (e: CertificateException) {
throw RuntimeException("Failed to init Cipher", e)
} catch (e: UnrecoverableKeyException) {
throw RuntimeException("Failed to init Cipher", e)
} catch (e: IOException) {
throw RuntimeException("Failed to init Cipher", e)
} catch (e: NoSuchAlgorithmException) {
throw RuntimeException("Failed to init Cipher", e)
} catch (e: InvalidKeyException) {
throw RuntimeException("Failed to init Cipher", e)
}
}
}
因此,我们完成了密钥生成、密码和密码对象。每当我们需要指纹认证时,就会调用 FingerprintManager 的authenticate方法,因此,可能会根据认证的失败或成功发生一些事件。因此,这些回调事件和身份验证方法必须在扩展 FingerprintManager.AuthenticationCallback 的类中实现。在您的项目中,添加一个名为FingerprintHelper.kt的类并添加以下代码行:
@SuppressLint("ByteOrderMark")
class FingerprintHelper(private val appContext: Context) : FingerprintManager.AuthenticationCallback() {
lateinit var cancellationSignal: CancellationSignal
fun startAuth(manager: FingerprintManager,
cryptoObject: FingerprintManager.CryptoObject) {
cancellationSignal = CancellationSignal()
if (ActivityCompat.checkSelfPermission(appContext,
Manifest.permission.USE_FINGERPRINT) !=
PackageManager.PERMISSION_GRANTED) {
return
}
manager.authenticate(cryptoObject, cancellationSignal, 0, this, null)
}
override fun onAuthenticationError(errMsgId: Int,
errString: CharSequence) {
Toast.makeText(appContext,
"Authentication error\n" + errString,
Toast.LENGTH_LONG).show()
}
override fun onAuthenticationHelp(helpMsgId: Int,
helpString: CharSequence) {
Toast.makeText(appContext,
"Authentication help\n" + helpString,
Toast.LENGTH_LONG).show()
}
override fun onAuthenticationFailed() {
Toast.makeText(appContext,
"Authentication failed.",
Toast.LENGTH_LONG).show()
}
override fun onAuthenticationSucceeded(
result: FingerprintManager.AuthenticationResult) {
Toast.makeText(appContext,
"Authentication succeeded.",
Toast.LENGTH_LONG).show()
}
}
现在,最后在 MainActivity 的 onCreate 方法中,我们必须创建 FingerprintHelper 类的新实例来启动startAuth方法。
override fun onCreate(savedInstanceState: Bundle) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
if (checkLockScreen()) {
generateKey()
if (initCipher()) {
cipher.let {
cryptoObject = FingerprintManager.CryptoObject(it)
}
val helper = FingerprintHelper(this)
if (fingerprintManager != null && cryptoObject != null) {
helper.startAuth(fingerprintManager, cryptoObject)
}
}
}
}
最后
在具有指纹传感器的设备上运行应用程序。另外,尝试不同的情况。例如,删除与您的设备关联的所有指纹,删除您设备的密码。