android生物特征识别用户指纹上的NotAuthenticationDexception

webghufk  于 2021-08-20  发布在  Java
关注(0)|答案(1)|浏览(565)

我在示例应用程序中使用secretkey进行mac签名。该键是使用生成器参数生成的

  1. setUserAuthenticationValidityDurationSeconds(10)

允许使用指纹和(取消)锁定设备pin来保护我的钥匙。
ui仅包含用于启动签名的签名按钮。
如果我通过“使用pin”使用生物识别提示,输入pin后我会收到签名(在我的示例“2bjeusxl/BOTTUEXE4VTX2RNRZEC1ZFA21FOOKBFNC=”)[注意:预期行为]。
在给定的10秒钟“validityduration”时间内再次按下sign按钮,我可以成功使用指纹进行授权[注意:预期行为]。
10秒后按下sign(签名)按钮并使用指纹进行授权[或在未事先使用pin的情况下使用指纹]会出现异常[注意:非预期行为]:

  1. android.security.keystore.UserNotAuthenticatedException: User not authenticated

因此,我的问题是:如何使用相同的secretkey来使用pin和fingerprint选项授权签名过程(或者更好地使用从androidkeystore释放密钥)?
我正在android sdk 30(目标)和(最低)23上进行测试,生物识别功能可通过“androidx.biometric:biometric:1.1.0”实现。
以下是logcat调试输出,以及我方的一些评论:

  1. >>> first start
  2. 2021-07-04 14:23:47.178 6980-6980/de.biometrics D/*** Biometric ***: sign started
  3. 2021-07-04 14:23:47.181 6980-6980/de.biometrics D/*** Biometric ***: try to load secretKey from keystore
  4. 2021-07-04 14:23:47.211 6980-6980/de.biometrics D/*** Biometric ***: generated fresh key, try to load
  5. 2021-07-04 14:23:47.223 6980-6980/de.biometrics D/*** Biometric ***: UserNotAuthenticatedException thrown, try to authenticate
  6. >>> biometric prompt, used the PIN [authType = 1]:
  7. 2021-07-04 14:24:04.089 6980-6980/de.biometrics D/*** Biometric ***: Authentication succeeded with authType 1
  8. 2021-07-04 14:24:04.089 6980-6980/de.biometrics D/*** Biometric ***: (authType: 1=PIN, 2=fingerprint)
  9. 2021-07-04 14:24:04.092 6980-6980/de.biometrics D/*** Biometric ***: try to load secretKey from keystore
  10. 2021-07-04 14:24:04.097 6980-6980/de.biometrics D/signed data: a1wbBdybQkP30XWFBj0o8fiVrS8BXlREGmDHQQQhEwg=
  11. >>> pressed "SIGN" again after 5 seconds
  12. 2021-07-04 14:24:09.725 6980-6980/de.biometrics D/*** Biometric ***: sign started
  13. 2021-07-04 14:24:09.730 6980-6980/de.biometrics D/*** Biometric ***: try to load secretKey from keystore
  14. 2021-07-04 14:24:13.421 6980-6980/de.biometrics D/*** Biometric ***: Authentication succeeded with authType 2
  15. 2021-07-04 14:24:13.422 6980-6980/de.biometrics D/*** Biometric ***: (authType: 1=PIN, 2=fingerprint)
  16. 2021-07-04 14:24:13.426 6980-6980/de.biometrics D/*** Biometric ***: try to load secretKey from keystore
  17. 2021-07-04 14:24:13.432 6980-6980/de.biometrics D/signed data: a1wbBdybQkP30XWFBj0o8fiVrS8BXlREGmDHQQQhEwg=
  18. >>> pressed "SIGN" again 21 seconds after the PIN authorization
  19. 2021-07-04 14:24:23.348 6980-6980/de.biometrics D/*** Biometric ***: sign started
  20. 2021-07-04 14:24:23.359 6980-6980/de.biometrics D/*** Biometric ***: try to load secretKey from keystore
  21. 2021-07-04 14:24:23.366 6980-6980/de.biometrics D/*** Biometric ***: UserNotAuthenticatedException thrown, try to authenticate
  22. >>> fingerprint is accepted [authType = 2]
  23. 2021-07-04 14:24:25.356 6980-6980/de.biometrics D/*** Biometric ***: Authentication succeeded with authType 2
  24. 2021-07-04 14:24:25.356 6980-6980/de.biometrics D/*** Biometric ***: (authType: 1=PIN, 2=fingerprint)
  25. 2021-07-04 14:24:25.361 6980-6980/de.biometrics D/*** Biometric ***: try to load secretKey from keystore
  26. >>> the exception is thrown on line 86:
  27. >>> mac.init(getOrCreateSecretKey(KEY_NAME_SIGN));
  28. 2021-07-04 14:24:25.377 6980-6980/de.biometrics W/System.err: android.security.keystore.UserNotAuthenticatedException: User not authenticated
  29. 2021-07-04 14:24:25.377 6980-6980/de.biometrics W/System.err: at android.security.KeyStore.getInvalidKeyException(KeyStore.java:1346)
  30. 2021-07-04 14:24:25.378 6980-6980/de.biometrics W/System.err: at android.security.KeyStore.getInvalidKeyException(KeyStore.java:1388)
  31. 2021-07-04 14:24:25.379 6980-6980/de.biometrics W/System.err: at android.security.keystore.KeyStoreCryptoOperationUtils.getInvalidKeyExceptionForInit(KeyStoreCryptoOperationUtils.java:54)
  32. 2021-07-04 14:24:25.379 6980-6980/de.biometrics W/System.err: at android.security.keystore.AndroidKeyStoreHmacSpi.ensureKeystoreOperationInitialized(AndroidKeyStoreHmacSpi.java:184)
  33. 2021-07-04 14:24:25.380 6980-6980/de.biometrics W/System.err: at android.security.keystore.AndroidKeyStoreHmacSpi.engineInit(AndroidKeyStoreHmacSpi.java:101)
  34. 2021-07-04 14:24:25.380 6980-6980/de.biometrics W/System.err: at javax.crypto.Mac.chooseProvider(Mac.java:443)
  35. 2021-07-04 14:24:25.380 6980-6980/de.biometrics W/System.err: at javax.crypto.Mac.init(Mac.java:513)
  36. 2021-07-04 14:24:25.380 6980-6980/de.biometrics W/System.err: at de.biometrics.MainActivity$1.onAuthenticationSucceeded(MainActivity.java:86)
  37. 2021-07-04 14:24:25.381 6980-6980/de.biometrics W/System.err: at androidx.biometric.BiometricFragment$9.run(BiometricFragment.java:907)
  38. 2021-07-04 14:24:25.381 6980-6980/de.biometrics W/System.err: at android.os.Handler.handleCallback(Handler.java:938)
  39. 2021-07-04 14:24:25.381 6980-6980/de.biometrics W/System.err: at android.os.Handler.dispatchMessage(Handler.java:99)
  40. 2021-07-04 14:24:25.382 6980-6980/de.biometrics W/System.err: at android.os.Looper.loop(Looper.java:223)
  41. 2021-07-04 14:24:25.384 6980-6980/de.biometrics W/System.err: at android.app.ActivityThread.main(ActivityThread.java:7656)
  42. 2021-07-04 14:24:25.385 6980-6980/de.biometrics W/System.err: at java.lang.reflect.Method.invoke(Native Method)
  43. 2021-07-04 14:24:25.386 6980-6980/de.biometrics W/System.err: at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
  44. 2021-07-04 14:24:25.386 6980-6980/de.biometrics W/System.err: at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)

这是完整的源代码(mainactivity.java):
编辑:我稍微修改了createkey函数,使其更符合上的文档https://developer.android.com/training/sign-in/biometric-auth#java

  1. package de.biometrics;
  2. import android.os.Bundle;
  3. import android.security.keystore.KeyGenParameterSpec;
  4. import android.security.keystore.KeyProperties;
  5. import android.security.keystore.UserNotAuthenticatedException;
  6. import android.util.Base64;
  7. import android.util.Log;
  8. import android.view.View;
  9. import android.widget.Button;
  10. import androidx.annotation.NonNull;
  11. import androidx.appcompat.app.AppCompatActivity;
  12. import androidx.biometric.BiometricPrompt;
  13. import androidx.core.content.ContextCompat;
  14. import java.io.IOException;
  15. import java.nio.charset.StandardCharsets;
  16. import java.security.InvalidAlgorithmParameterException;
  17. import java.security.InvalidKeyException;
  18. import java.security.KeyStore;
  19. import java.security.KeyStoreException;
  20. import java.security.NoSuchAlgorithmException;
  21. import java.security.NoSuchProviderException;
  22. import java.security.UnrecoverableKeyException;
  23. import java.security.cert.CertificateException;
  24. import java.util.concurrent.Executor;
  25. import javax.crypto.KeyGenerator;
  26. import javax.crypto.Mac;
  27. import javax.crypto.SecretKey;
  28. public class MainActivity extends AppCompatActivity {
  29. // use dependency in build.graddle:
  30. // implementation 'androidx.biometric:biometric:1.1.0'
  31. private static final String KEY_NAME_SIGN = "SignKey";
  32. private static final int VALIDITY_DURATION_SECONDS = 10;
  33. private static final String APP_TAG = "***Biometric***";
  34. private BiometricPrompt biometricPrompt;
  35. private BiometricPrompt.PromptInfo promptInfo;
  36. private Executor executor;
  37. Button btnSign;
  38. Mac mac;
  39. @Override
  40. protected void onCreate(Bundle savedInstanceState) {
  41. super.onCreate(savedInstanceState);
  42. setContentView(R.layout.activity_main);
  43. executor = ContextCompat.getMainExecutor(this);
  44. btnSign = (Button) findViewById(R.id.button);
  45. // Allows user to authenticate using either a Class 3 biometric or
  46. // their lock screen credential (PIN, pattern, or password).
  47. promptInfo = new BiometricPrompt.PromptInfo.Builder()
  48. .setTitle("Biometric login for my app")
  49. .setSubtitle("Log in using your biometric credential")
  50. // Can't call setNegativeButtonText() and
  51. // setAllowedAuthenticators(...|DEVICE_CREDENTIAL) at the same time.
  52. // .setNegativeButtonText("Use account password")
  53. .setAllowedAuthenticators(
  54. androidx.biometric.BiometricManager.Authenticators.BIOMETRIC_STRONG
  55. | androidx.biometric.BiometricManager.Authenticators.DEVICE_CREDENTIAL)
  56. .build();
  57. biometricPrompt = new BiometricPrompt(MainActivity.this,
  58. executor, new BiometricPrompt.AuthenticationCallback() {
  59. @Override
  60. public void onAuthenticationError(int errorCode,
  61. @NonNull CharSequence errString) {
  62. super.onAuthenticationError(errorCode, errString);
  63. Log.d(APP_TAG, "Authentication succeeded!");
  64. }
  65. @Override
  66. public void onAuthenticationSucceeded(
  67. @NonNull BiometricPrompt.AuthenticationResult result) {
  68. super.onAuthenticationSucceeded(result);
  69. int authorizationType = result.getAuthenticationType();
  70. Log.d(APP_TAG, "Authentication succeeded with authType " + authorizationType);
  71. Log.d(APP_TAG, "(authType: 1=PIN, 2=fingerprint)");
  72. try {
  73. // init mac from scratch
  74. mac = Mac.getInstance("HmacSHA256");
  75. mac.init(getOrCreateSecretKey(KEY_NAME_SIGN));
  76. byte[] bytes = "secret-text".getBytes(StandardCharsets.UTF_8);
  77. byte[] macResult = mac.doFinal(bytes);
  78. Log.d("signed data ", Base64.encodeToString(macResult, Base64.NO_WRAP));
  79. } catch (InvalidKeyException | NoSuchAlgorithmException e) {
  80. e.printStackTrace();
  81. }
  82. }
  83. @Override
  84. public void onAuthenticationFailed() {
  85. super.onAuthenticationFailed();
  86. Log.d(APP_TAG, "Authentication failed");
  87. }
  88. });
  89. btnSign.setOnClickListener(new View.OnClickListener() {
  90. @Override
  91. public void onClick(View v) {
  92. sign();
  93. }
  94. });
  95. }
  96. private void sign() {
  97. // simple sign function
  98. Log.d(APP_TAG, "sign started");
  99. // setup the mac
  100. try {
  101. mac = Mac.getInstance("HmacSHA256");
  102. mac.init(getOrCreateSecretKey(KEY_NAME_SIGN));
  103. } catch (UserNotAuthenticatedException e) {
  104. Log.d(APP_TAG, "UserNotAuthenticatedException thrown, try to authenticate");
  105. biometricPrompt.authenticate(promptInfo);
  106. } catch (NoSuchAlgorithmException | InvalidKeyException e) {
  107. e.printStackTrace();
  108. }
  109. biometricPrompt.authenticate(promptInfo);
  110. }
  111. private SecretKey getOrCreateSecretKey(String keyName) {
  112. SecretKey secretKey = getSecretKey(keyName);
  113. Log.d(APP_TAG, "try to load secretKey from keystore");
  114. if (secretKey == null) {
  115. createSecretKey(keyName);
  116. secretKey = getSecretKey(keyName);
  117. Log.d(APP_TAG, "generated fresh key, try to load");
  118. }
  119. return secretKey;
  120. }
  121. private SecretKey getSecretKey(String keyName) {
  122. KeyStore keyStore = null;
  123. try {
  124. keyStore = KeyStore.getInstance("AndroidKeyStore");
  125. // Before the keystore can be accessed, it must be loaded.
  126. keyStore.load(null);
  127. return ((SecretKey) keyStore.getKey(keyName, null));
  128. } catch (KeyStoreException | CertificateException | UnrecoverableKeyException | NoSuchAlgorithmException | IOException e) {
  129. e.printStackTrace();
  130. return null;
  131. }
  132. }
  133. private void createSecretKey(String keyName) {
  134. generateSecretKey(new KeyGenParameterSpec.Builder(
  135. keyName,
  136. KeyProperties.PURPOSE_SIGN)
  137. .setUserAuthenticationRequired(true)
  138. //.setInvalidatedByBiometricEnrollment(true)
  139. .setUserAuthenticationValidityDurationSeconds(10)
  140. .build());
  141. }// All exceptions unhandled
  142. private void generateSecretKey(KeyGenParameterSpec keyGenParameterSpec) {
  143. KeyGenerator keyGenerator = null;
  144. try {
  145. keyGenerator = KeyGenerator.getInstance(
  146. KeyProperties.KEY_ALGORITHM_HMAC_SHA256, "AndroidKeyStore");
  147. keyGenerator.init(keyGenParameterSpec);
  148. keyGenerator.generateKey();
  149. } catch (NoSuchAlgorithmException | InvalidAlgorithmParameterException | NoSuchProviderException e) {
  150. e.printStackTrace();
  151. }
  152. }
  153. }

activity_main.xml

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3. xmlns:app="http://schemas.android.com/apk/res-auto"
  4. xmlns:tools="http://schemas.android.com/tools"
  5. android:layout_width="match_parent"
  6. android:layout_height="match_parent"
  7. tools:context=".MainActivity">
  8. <Button
  9. android:id="@+id/button"
  10. android:layout_width="wrap_content"
  11. android:layout_height="wrap_content"
  12. android:layout_marginStart="16dp"
  13. android:layout_marginTop="32dp"
  14. android:layout_marginEnd="16dp"
  15. android:text="Sign"
  16. app:layout_constraintEnd_toEndOf="parent"
  17. app:layout_constraintStart_toStartOf="parent"
  18. app:layout_constraintTop_toTopOf="parent" />
  19. </androidx.constraintlayout.widget.ConstraintLayout>

build.graddle(模块):

  1. plugins {
  2. id 'com.android.application'
  3. }
  4. android {
  5. compileSdkVersion 30
  6. buildToolsVersion "30.0.3"
  7. defaultConfig {
  8. applicationId "de.biometrics"
  9. minSdkVersion 23
  10. targetSdkVersion 30
  11. versionCode 1
  12. versionName "1.0"
  13. testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
  14. }
  15. buildTypes {
  16. release {
  17. minifyEnabled false
  18. proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
  19. }
  20. }
  21. compileOptions {
  22. sourceCompatibility JavaVersion.VERSION_1_8
  23. targetCompatibility JavaVersion.VERSION_1_8
  24. }
  25. }
  26. dependencies {
  27. implementation 'androidx.appcompat:appcompat:1.3.0'
  28. implementation 'com.google.android.material:material:1.3.0'
  29. implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
  30. testImplementation 'junit:junit:4.+'
  31. androidTestImplementation 'androidx.test.ext:junit:1.1.3'
  32. androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
  33. implementation 'androidx.biometric:biometric:1.1.0'
  34. }

注意:这只是一个非常简化的程序,仅用于测试生物特征提示功能。

6vl6ewon

6vl6ewon1#

我在回答我自己的问题,因为我的发现可能对其他人有所帮助。
到目前为止(2021年7月6日),我不知道为什么使用该选项生成secretkey

  1. .setUserAuthenticationValidityDurationSeconds(10);

无法先通过指纹释放。正如我在问题中所写的,当它首先通过pin(设备凭证)发布时,只要持续时间未过期,它就可以通过指纹使用(“身份验证”)。
无论如何,文档中还有另一个选项可用,即使用每次使用的身份验证密钥进行身份验证(https://developer.android.com/training/sign-in/biometric-auth#auth-每次使用密钥),此选项提供预期结果。
当用新代码更新代码时,不要忘记生成一个新的密钥!
使用新选项时

  1. .setUserAuthenticationParameters(0 /* duration */,
  2. KeyProperties.AUTH_BIOMETRIC_STRONG |
  3. KeyProperties.AUTH_DEVICE_CREDENTIAL)

您会注意到,代码需要sdk 30+才能运行。对于在sdk>23上运行的代码,可以使用

  1. .setUserAuthenticationValidityDurationSeconds(0)

使用“0”秒很重要,因为这(内部)默认为“生物特征|设备|凭证”,使用“-1”时默认为“设备|凭证”(不带指纹选项)。
下面我提供的代码用于检查正在使用的sdk并选择正确的generatekey函数:

  1. private SecretKey getOrCreateSecretKey(String keyName) {
  2. SecretKey secretKey = getSecretKey(keyName);
  3. Log.d(APP_TAG, "try to load secretKey from keystore");
  4. if (secretKey == null) {
  5. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M &
  6. Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
  7. createSecretKeyApi2329(keyName);
  8. Log.d(APP_TAG, "createSecretKeyApi2329 SDK in use: " + Build.VERSION.SDK_INT);
  9. }
  10. if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
  11. createSecretKeyApi30(keyName);
  12. Log.d(APP_TAG, "createSecretKeyApi30 SDK in use: " + Build.VERSION.SDK_INT);
  13. }
  14. // as minimum SDK in build.gradle was set to 23 the version can't be below 23
  15. if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
  16. Log.d(APP_TAG, "SDK in use is to old, minimum SDK is 23 = M");
  17. finish();
  18. }
  19. secretKey = getSecretKey(keyName);
  20. Log.d(APP_TAG, "generated fresh key, try to load");
  21. }
  22. return secretKey;
  23. }
  24. @RequiresApi(api = Build.VERSION_CODES.R)
  25. private void createSecretKeyApi30(String keyName) {
  26. generateSecretKey(new KeyGenParameterSpec.Builder(
  27. keyName,
  28. KeyProperties.PURPOSE_SIGN)
  29. //.setInvalidatedByBiometricEnrollment(true)
  30. // Accept either a biometric credential or a device credential.
  31. // To accept only one type of credential, include only that type as the
  32. // second argument.
  33. // @RequiresApi(api = Build.VERSION_CODES.R)
  34. .setUserAuthenticationParameters(0 /* duration */,
  35. KeyProperties.AUTH_BIOMETRIC_STRONG |
  36. KeyProperties.AUTH_DEVICE_CREDENTIAL)
  37. .build());
  38. }// All exceptions unhandled
  39. //@RequiresApi(api = Build.VERSION_CODES.M)
  40. private void createSecretKeyApi2329(String keyName) {
  41. generateSecretKey(new KeyGenParameterSpec.Builder(
  42. keyName,
  43. KeyProperties.PURPOSE_SIGN)
  44. //.setInvalidatedByBiometricEnrollment(true)
  45. // Accept either a biometric credential or a device credential.
  46. // To accept only one type of credential, include only that type as the
  47. // second argument.
  48. // for SDK < 30 use .setUserAuthenticationValidityDurationSeconds(0)
  49. // see https://cs.android.com/android/platform/superproject/+/android-11.0.0_r3:frameworks/base/keystore/java/android/security/keystore/KeyGenParameterSpec.java;l=1236-1246;drc=a811787a9642e6a9e563f2b7dfb15b5ae27ebe98
  50. // parameter "0" defaults to AUTH_BIOMETRIC_STRONG | AUTH_DEVICE_CREDENTIAL
  51. // parameter "-1" default to AUTH_BIOMETRIC_STRONG
  52. .setUserAuthenticationValidityDurationSeconds(0)
  53. .build());
  54. }// All exceptions unhandled
展开查看全部

相关问题