首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >基于指纹的安卓生物识别技术UserNotAuthenticatedException

基于指纹的安卓生物识别技术UserNotAuthenticatedException
EN

Stack Overflow用户
提问于 2021-07-04 13:41:48
回答 1查看 1.1K关注 0票数 0

我在我的示例应用程序中使用SecretKey进行Mac签名。键是用构建器参数生成的。

代码语言:javascript
复制
setUserAuthenticationValidityDurationSeconds(10)

为了允许使用指纹,(Un)锁定设备PIN来保护我的密钥。

UI只包含一个启动签名的符号按钮。

如果我通过“using”使用生物识别提示符,在输入PIN之后,我将收到签名(在我的示例"2BjEuSxl/bOTTUExE4vTX2rnRZEC1Zfa21FooKkBfnc=")说明中:预期行为)。

在给定的10秒"ValidityDuration“时间内再次按下签名按钮,我可以成功地使用指纹进行授权,注意:预期行为。

在10秒后按下签名按钮,然后使用指纹进行授权或使用指纹,而无需事先使用PIN,这是一个例外说明:不是预期的行为。

代码语言:javascript
复制
android.security.keystore.UserNotAuthenticatedException: User not authenticated

因此,我的问题是:如何使用 SecretKey来授权使用PIN和指纹选项的签名过程(或者更好地从AndroidKeystore中释放密钥)?

我正在Android 30 (target)和(最低) 23上进行测试,生物识别功能可以通过实现‘Android。

下面是Logcat调试输出,我这边有一些注释:

代码语言:javascript
复制
>>> first start
2021-07-04 14:23:47.178 6980-6980/de.biometrics D/*** Biometric ***: sign started
2021-07-04 14:23:47.181 6980-6980/de.biometrics D/*** Biometric ***: try to load secretKey from keystore
2021-07-04 14:23:47.211 6980-6980/de.biometrics D/*** Biometric ***: generated fresh key, try to load
2021-07-04 14:23:47.223 6980-6980/de.biometrics D/*** Biometric ***: UserNotAuthenticatedException thrown, try to authenticate

>>> biometric prompt, used the PIN [authType = 1]:
2021-07-04 14:24:04.089 6980-6980/de.biometrics D/*** Biometric ***: Authentication succeeded with authType 1
2021-07-04 14:24:04.089 6980-6980/de.biometrics D/*** Biometric ***: (authType: 1=PIN, 2=fingerprint)
2021-07-04 14:24:04.092 6980-6980/de.biometrics D/*** Biometric ***: try to load secretKey from keystore
2021-07-04 14:24:04.097 6980-6980/de.biometrics D/signed data: a1wbBdybQkP30XWFBj0o8fiVrS8BXlREGmDHQQQhEwg=

>>> pressed "SIGN" again after 5 seconds
2021-07-04 14:24:09.725 6980-6980/de.biometrics D/*** Biometric ***: sign started
2021-07-04 14:24:09.730 6980-6980/de.biometrics D/*** Biometric ***: try to load secretKey from keystore
2021-07-04 14:24:13.421 6980-6980/de.biometrics D/*** Biometric ***: Authentication succeeded with authType 2
2021-07-04 14:24:13.422 6980-6980/de.biometrics D/*** Biometric ***: (authType: 1=PIN, 2=fingerprint)
2021-07-04 14:24:13.426 6980-6980/de.biometrics D/*** Biometric ***: try to load secretKey from keystore
2021-07-04 14:24:13.432 6980-6980/de.biometrics D/signed data: a1wbBdybQkP30XWFBj0o8fiVrS8BXlREGmDHQQQhEwg=

>>> pressed "SIGN" again 21 seconds after the PIN authorization
2021-07-04 14:24:23.348 6980-6980/de.biometrics D/*** Biometric ***: sign started
2021-07-04 14:24:23.359 6980-6980/de.biometrics D/*** Biometric ***: try to load secretKey from keystore
2021-07-04 14:24:23.366 6980-6980/de.biometrics D/*** Biometric ***: UserNotAuthenticatedException thrown, try to authenticate
>>> fingerprint is accepted [authType = 2]
2021-07-04 14:24:25.356 6980-6980/de.biometrics D/*** Biometric ***: Authentication succeeded with authType 2
2021-07-04 14:24:25.356 6980-6980/de.biometrics D/*** Biometric ***: (authType: 1=PIN, 2=fingerprint)
2021-07-04 14:24:25.361 6980-6980/de.biometrics D/*** Biometric ***: try to load secretKey from keystore
>>> the exception is thrown on line 86:
>>> mac.init(getOrCreateSecretKey(KEY_NAME_SIGN));
2021-07-04 14:24:25.377 6980-6980/de.biometrics W/System.err: android.security.keystore.UserNotAuthenticatedException: User not authenticated
2021-07-04 14:24:25.377 6980-6980/de.biometrics W/System.err:     at android.security.KeyStore.getInvalidKeyException(KeyStore.java:1346)
2021-07-04 14:24:25.378 6980-6980/de.biometrics W/System.err:     at android.security.KeyStore.getInvalidKeyException(KeyStore.java:1388)
2021-07-04 14:24:25.379 6980-6980/de.biometrics W/System.err:     at android.security.keystore.KeyStoreCryptoOperationUtils.getInvalidKeyExceptionForInit(KeyStoreCryptoOperationUtils.java:54)
2021-07-04 14:24:25.379 6980-6980/de.biometrics W/System.err:     at android.security.keystore.AndroidKeyStoreHmacSpi.ensureKeystoreOperationInitialized(AndroidKeyStoreHmacSpi.java:184)
2021-07-04 14:24:25.380 6980-6980/de.biometrics W/System.err:     at android.security.keystore.AndroidKeyStoreHmacSpi.engineInit(AndroidKeyStoreHmacSpi.java:101)
2021-07-04 14:24:25.380 6980-6980/de.biometrics W/System.err:     at javax.crypto.Mac.chooseProvider(Mac.java:443)
2021-07-04 14:24:25.380 6980-6980/de.biometrics W/System.err:     at javax.crypto.Mac.init(Mac.java:513)
2021-07-04 14:24:25.380 6980-6980/de.biometrics W/System.err:     at de.biometrics.MainActivity$1.onAuthenticationSucceeded(MainActivity.java:86)
2021-07-04 14:24:25.381 6980-6980/de.biometrics W/System.err:     at androidx.biometric.BiometricFragment$9.run(BiometricFragment.java:907)
2021-07-04 14:24:25.381 6980-6980/de.biometrics W/System.err:     at android.os.Handler.handleCallback(Handler.java:938)
2021-07-04 14:24:25.381 6980-6980/de.biometrics W/System.err:     at android.os.Handler.dispatchMessage(Handler.java:99)
2021-07-04 14:24:25.382 6980-6980/de.biometrics W/System.err:     at android.os.Looper.loop(Looper.java:223)
2021-07-04 14:24:25.384 6980-6980/de.biometrics W/System.err:     at android.app.ActivityThread.main(ActivityThread.java:7656)
2021-07-04 14:24:25.385 6980-6980/de.biometrics W/System.err:     at java.lang.reflect.Method.invoke(Native Method)
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)
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):

编辑:i稍微修改createKey-函数,使其更符合https://developer.android.com/training/sign-in/biometric-auth#java上的文档

代码语言:javascript
复制
package de.biometrics;

import android.os.Bundle;
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyProperties;
import android.security.keystore.UserNotAuthenticatedException;
import android.util.Base64;
import android.util.Log;
import android.view.View;
import android.widget.Button;

import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.biometric.BiometricPrompt;
import androidx.core.content.ContextCompat;

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.util.concurrent.Executor;

import javax.crypto.KeyGenerator;
import javax.crypto.Mac;
import javax.crypto.SecretKey;

public class MainActivity extends AppCompatActivity {
    // use dependency in build.graddle:
    // implementation 'androidx.biometric:biometric:1.1.0'

    private static final String KEY_NAME_SIGN = "SignKey";
    private static final int VALIDITY_DURATION_SECONDS = 10;
    private static final String APP_TAG = "*** Biometric *** ";

    private BiometricPrompt biometricPrompt;
    private BiometricPrompt.PromptInfo promptInfo;
    private Executor executor;
    Button btnSign;
    Mac mac;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        executor = ContextCompat.getMainExecutor(this);
        btnSign = (Button) findViewById(R.id.button);

        // Allows user to authenticate using either a Class 3 biometric or
        // their lock screen credential (PIN, pattern, or password).
        promptInfo = new BiometricPrompt.PromptInfo.Builder()
                .setTitle("Biometric login for my app")
                .setSubtitle("Log in using your biometric credential")
                // Can't call setNegativeButtonText() and
                // setAllowedAuthenticators(...|DEVICE_CREDENTIAL) at the same time.
                // .setNegativeButtonText("Use account password")
                .setAllowedAuthenticators(
                        androidx.biometric.BiometricManager.Authenticators.BIOMETRIC_STRONG
                                | androidx.biometric.BiometricManager.Authenticators.DEVICE_CREDENTIAL)
                .build();

        biometricPrompt = new BiometricPrompt(MainActivity.this,
                executor, new BiometricPrompt.AuthenticationCallback() {
            @Override
            public void onAuthenticationError(int errorCode,
                                              @NonNull CharSequence errString) {
                super.onAuthenticationError(errorCode, errString);
                Log.d(APP_TAG, "Authentication succeeded!");
            }
            @Override
            public void onAuthenticationSucceeded(
                    @NonNull BiometricPrompt.AuthenticationResult result) {
                super.onAuthenticationSucceeded(result);
                int authorizationType = result.getAuthenticationType();
                Log.d(APP_TAG, "Authentication succeeded with authType " + authorizationType);
                Log.d(APP_TAG, "(authType: 1=PIN, 2=fingerprint)");
                try {
                    // init mac from scratch
                    mac = Mac.getInstance("HmacSHA256");
                    mac.init(getOrCreateSecretKey(KEY_NAME_SIGN));
                    byte[] bytes = "secret-text".getBytes(StandardCharsets.UTF_8);
                    byte[] macResult = mac.doFinal(bytes);
                    Log.d("signed data ", Base64.encodeToString(macResult, Base64.NO_WRAP));
                } catch (InvalidKeyException | NoSuchAlgorithmException e) {
                    e.printStackTrace();
                }
            }
            @Override
            public void onAuthenticationFailed() {
                super.onAuthenticationFailed();
                Log.d(APP_TAG, "Authentication failed");
            }
        });

        btnSign.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                sign();
            }
        });
    }

    private void sign() {
        // simple sign function
        Log.d(APP_TAG, "sign started");
        // setup the mac
        try {
            mac = Mac.getInstance("HmacSHA256");
            mac.init(getOrCreateSecretKey(KEY_NAME_SIGN));
        } catch (UserNotAuthenticatedException e) {
            Log.d(APP_TAG, "UserNotAuthenticatedException thrown, try to authenticate");
            biometricPrompt.authenticate(promptInfo);
        } catch (NoSuchAlgorithmException | InvalidKeyException e) {
            e.printStackTrace();
        }
        biometricPrompt.authenticate(promptInfo);
    }

    private SecretKey getOrCreateSecretKey(String keyName) {
        SecretKey secretKey = getSecretKey(keyName);
        Log.d(APP_TAG, "try to load secretKey from keystore");
        if (secretKey == null) {
            createSecretKey(keyName);
            secretKey = getSecretKey(keyName);
            Log.d(APP_TAG, "generated fresh key, try to load");
        }
        return secretKey;
    }

    private SecretKey getSecretKey(String keyName) {
        KeyStore keyStore = null;
        try {
            keyStore = KeyStore.getInstance("AndroidKeyStore");
            // Before the keystore can be accessed, it must be loaded.
            keyStore.load(null);
            return ((SecretKey) keyStore.getKey(keyName, null));
        } catch (KeyStoreException | CertificateException | UnrecoverableKeyException | NoSuchAlgorithmException | IOException e) {
            e.printStackTrace();
            return null;
        }
    }

private void createSecretKey(String keyName) {
        generateSecretKey(new KeyGenParameterSpec.Builder(
                keyName,
                KeyProperties.PURPOSE_SIGN)
                .setUserAuthenticationRequired(true)
                //.setInvalidatedByBiometricEnrollment(true)
                .setUserAuthenticationValidityDurationSeconds(10)
                .build());
    }// All exceptions unhandled

    private void generateSecretKey(KeyGenParameterSpec keyGenParameterSpec) {
        KeyGenerator keyGenerator = null;
        try {
            keyGenerator = KeyGenerator.getInstance(
                    KeyProperties.KEY_ALGORITHM_HMAC_SHA256, "AndroidKeyStore");
            keyGenerator.init(keyGenParameterSpec);
            keyGenerator.generateKey();
        } catch (NoSuchAlgorithmException | InvalidAlgorithmParameterException | NoSuchProviderException e) {
            e.printStackTrace();
        }
    }
}

activity_main.xml

代码语言:javascript
复制
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/button"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginStart="16dp"
        android:layout_marginTop="32dp"
        android:layout_marginEnd="16dp"
        android:text="Sign"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>

build.graddle (模块):

代码语言:javascript
复制
plugins {
    id 'com.android.application'
}

android {
    compileSdkVersion 30
    buildToolsVersion "30.0.3"

    defaultConfig {
        applicationId "de.biometrics"
        minSdkVersion 23
        targetSdkVersion 30
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
}

dependencies {

    implementation 'androidx.appcompat:appcompat:1.3.0'
    implementation 'com.google.android.material:material:1.3.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
    testImplementation 'junit:junit:4.+'
    androidTestImplementation 'androidx.test.ext:junit:1.1.3'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
    implementation 'androidx.biometric:biometric:1.1.0'
}

注意:这只是一个非常简单的程序,只是为了测试生物识别提示功能。

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2021-07-06 08:32:24

我在回答我自己的问题,因为我的发现可能对其他人有帮助。

到目前为止(2021年7月6日),我不知道为什么使用该选项生成的SecretKey

代码语言:javascript
复制
.setUserAuthenticationValidityDurationSeconds(10);

无法首先通过指纹获得释放的。正如在我的问题中所写的,当PIN (设备凭据)首先发布它时,它可以通过指纹获得使用的(“身份验证”),只要持续时间不超过。

无论如何,docs 中还有另一个可用的选项,即使用auth-per键 (https://developer.android.com/training/sign-in/biometric-auth#auth-per-use-keys)进行身份验证,并且该选项提供了预期的结果。

在用新代码更新代码时,不要忘记生成一个新的密钥

使用新选项时

代码语言:javascript
复制
.setUserAuthenticationParameters(0 /* duration */,
   KeyProperties.AUTH_BIOMETRIC_STRONG |
   KeyProperties.AUTH_DEVICE_CREDENTIAL)

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

代码语言:javascript
复制
.setUserAuthenticationValidityDurationSeconds(0)

使用"0“秒很重要,因为在使用"-1”默认为“DEVICE_CREDENTIAL”时(内部)默认为"DEVICE_CREDENTIAL“(没有指纹选项)。

下面我提供了检查使用中的SDK并选择正确的generateKey-函数的代码:

代码语言:javascript
复制
private SecretKey getOrCreateSecretKey(String keyName) {
    SecretKey secretKey = getSecretKey(keyName);
    Log.d(APP_TAG, "try to load secretKey from keystore");
    if (secretKey == null) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M &
                Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
            createSecretKeyApi2329(keyName);
            Log.d(APP_TAG, "createSecretKeyApi2329 SDK in use:  " + Build.VERSION.SDK_INT);
        }
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
            createSecretKeyApi30(keyName);
            Log.d(APP_TAG, "createSecretKeyApi30 SDK in use:  " + Build.VERSION.SDK_INT);
        }
        // as minimum SDK in build.gradle was set to 23 the version can't be below 23
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
            Log.d(APP_TAG, "SDK in use is to old, minimum SDK is 23 = M");
            finish();
        }
        secretKey = getSecretKey(keyName);
        Log.d(APP_TAG, "generated fresh key, try to load");
    }
    return secretKey;
}

@RequiresApi(api = Build.VERSION_CODES.R)
private void createSecretKeyApi30(String keyName) {
    generateSecretKey(new KeyGenParameterSpec.Builder(
            keyName,
            KeyProperties.PURPOSE_SIGN)
            //.setInvalidatedByBiometricEnrollment(true)
            // Accept either a biometric credential or a device credential.
            // To accept only one type of credential, include only that type as the
            // second argument.
            // @RequiresApi(api = Build.VERSION_CODES.R)
            .setUserAuthenticationParameters(0 /* duration */,
                    KeyProperties.AUTH_BIOMETRIC_STRONG |
                            KeyProperties.AUTH_DEVICE_CREDENTIAL)
            .build());
}// All exceptions unhandled

//@RequiresApi(api = Build.VERSION_CODES.M)
private void createSecretKeyApi2329(String keyName) {
    generateSecretKey(new KeyGenParameterSpec.Builder(
            keyName,
            KeyProperties.PURPOSE_SIGN)
            //.setInvalidatedByBiometricEnrollment(true)
            // Accept either a biometric credential or a device credential.
            // To accept only one type of credential, include only that type as the
            // second argument.
            // for SDK < 30 use .setUserAuthenticationValidityDurationSeconds(0)
            // 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
            // parameter "0" defaults to AUTH_BIOMETRIC_STRONG | AUTH_DEVICE_CREDENTIAL
            // parameter "-1" default to AUTH_BIOMETRIC_STRONG
            .setUserAuthenticationValidityDurationSeconds(0)
            .build());
}// All exceptions unhandled
票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/68245293

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档