FingerprintManager.authenticate() 期间的 UserNotAuthenticatedException

UserNotAuthenticatedException during FingerprintManager.authenticate()

提问人:muetzenflo 提问时间:8/22/2016 最后编辑:muetzenflo 更新时间:6/18/2018 访问量:3376

问:

我有一个加密的密码存储在 Android KeyStore 中。

我想通过使用指纹 API 对用户进行身份验证来解密该密码。

据我了解,我必须调用该方法才能开始监听指纹结果。CryptoObject 参数的创建方式如下:FingerprintManager.authenticate(CryptoObject cryptoObject)

public static Cipher getDecryptionCipher(Context context) throws KeyStoreException {
    try {
        Cipher cipher = Cipher.getInstance(TRANSFORMATION);
        SecretKey secretKey = getKeyFromKeyStore();
        final IvParameterSpec ivParameterSpec = getIvParameterSpec(context);

        cipher.init(Cipher.DECRYPT_MODE, secretKey, ivParameterSpec);
        return cipher;

    } catch (NoSuchAlgorithmException | NoSuchPaddingException | IOException | UnrecoverableKeyException | CertificateException | InvalidAlgorithmParameterException | InvalidKeyException e) {
        e.printStackTrace();

    }

    return null;
}

Cipher cipher = FingerprintCryptoHelper.getDecryptionCipher(getContext());
FingerprintManager.CryptoObject cryptoObject = new FingerprintManager.CryptoObject(cipher);
fingerprintManager.authenticate(cryptoObject, ...);

该方法在调用之前正常工作。在此调用中,我得到一个 ,因为用户未对此 secretKey 进行身份验证。这在某种程度上是有道理的。但这难道不是一个无法实现的循环吗:getDecryptionCipher()cipher.init()UserNotAuthenticatedException

  • 要对用户进行身份验证,我想使用他/她的指纹
  • 为了监听他/她的指纹,我需要初始化密码,而密码又需要一个经过身份验证的用户

这里怎么了??

编辑:

我使用模拟器(Nexus 4,API 23)。

下面是我用来创建密钥的代码。

private SecretKey createKey() {
    try {
        KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, ANDROID_KEY_STORE);
        keyGenerator.init(new KeyGenParameterSpec.Builder(
                KEY_NAME,
                KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT
        )
                .setBlockModes(KeyProperties.BLOCK_MODE_CBC)
                .setUserAuthenticationRequired(true)
                .setUserAuthenticationValidityDurationSeconds(AUTHENTICATION_DURATION_SECONDS)
                .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7)
                .build());
        return keyGenerator.generateKey();
    } catch (NoSuchAlgorithmException | NoSuchProviderException | InvalidAlgorithmParameterException e) {
        throw new RuntimeException("Failed to create a symmetric key", e);
    }
}
安卓 android-fingerprint-api

评论

0赞 Michael 9/5/2016
您在哪台设备上进行测试?密钥是如何生成的?
0赞 Michael 9/5/2016
并且您已在模拟器中注册了至少一个指纹?
0赞 muetzenflo 1/16/2018
@Michael 它在各种设备上进行了测试,从Nexus设备到一些三星和HTC One Mini。是的,我也在模拟器上测试了它。

答:

1赞 muetzenflo 9/5/2016 #1

事实证明,该问题与一个已知问题有关,该问题阻止了未经身份验证使用公钥(这正是公钥不需要的)。KeyGenParameterSpec

相关的问题/答案可以在这里找到: Android 指纹 API 加密和解密

解决方法是从最初创建的密钥创建一个,并使用此不受限制的 PublicKey 初始化密码。 因此,我的最终密码使用 AES/CBC/PKCS7Padding 并通过此方法初始化:PublicKey

public boolean initCipher(int opMode) {
    try {
        Key key = mKeyStore.getKey(KEY_NAME, null);

        if (opMode == Cipher.ENCRYPT_MODE) {
            final byte[] encoded = key.getEncoded();
            final String algorithm = key.getAlgorithm();
            final X509EncodedKeySpec keySpec = new X509EncodedKeySpec(encoded);
            PublicKey unrestricted = KeyFactory.getInstance(algorithm).generatePublic(keySpec);

            mCipher.init(opMode, unrestricted);

        } else {
            final IvParameterSpec ivParameterSpec = getIvParameterSpec();
            mCipher.init(opMode, key, ivParameterSpec);

        }

        return true;

    } catch (KeyPermanentlyInvalidatedException exception) {
        return false;

    } catch ( NoSuchAlgorithmException | InvalidKeyException
            | InvalidKeySpecException | InvalidAlgorithmParameterException | UnrecoverableKeyException | KeyStoreException exception) {
        throw new RuntimeException("Failed to initialize Cipher or Key: ", exception);
    }
}

@NonNull
public IvParameterSpec getIvParameterSpec() {
    // the IV is stored in the Preferences after encoding.
    String base64EncryptionIv = PreferenceHelper.getEncryptionIv(mContext);
    byte[] encryptionIv = Base64.decode(base64EncryptionIv, Base64.DEFAULT);
    return new IvParameterSpec(encryptionIv);
}

评论

1赞 UKoehler 1/16/2018
这真的有效吗?您使用 AES,这是一种没有公钥的对称算法。如何从 AES 密钥生成公钥?
0赞 muetzenflo 1/16/2018
它确实有效,但我真的无法解释为什么。我和你提到的想法是一样的,但这是我把它写成“按预期”表达的唯一方法。请随时改进此方法!
1赞 Michael 1/16/2018
我不明白这如何与 AES 密钥一起使用。首先,很有可能返回 /,以避免将此类关键材料暴露在 TEE 之外。即使这里不是这种情况,AES 也应该失败,因为它只支持 DH、DSA、EC、RSA 和 X.509。在我看来,您实际上在这里使用了其他一些密钥。getEncodednullSecretKeyPrivateKeyKeyFactory.getInstance
0赞 Pierre-Olivier Dybman 10/24/2018
它确实返回 null
6赞 ssuukk 6/18/2018 #2

我找到了逃脱第22条军规的方法!

你这样做是这样的:

你像往常一样try{.initCipher

  1. 如果没有(因为用户在密钥的有效期内进行了身份验证,即因为他在几秒钟前解锁了他的设备),则执行您的加密/解密例程。结束!UserNotAuthenticatedException

  2. 你抓住了 - 运行工作流 (yes!) ,然后在回调中再次初始化你的密码 (yes!),但这次它不会抛出并使用这个初始化的实例来加密/解密(回调返回,就像我们用 CryptoObject 调用它一样,所以我们不能使用它)。结束!UserNotAuthenticatedExceptionFingerprintManager.authenticatenullCryptoObjectonAuthenticationSucceededUserNotAuthenticatedExceptionnullnull

就这么简单......

但是我花了两天时间才通过跟踪和错误找到这种方法。更不用说 - 似乎网上存在的所有身份验证示例都是错误的!

评论

1赞 muetzenflo 11/21/2018
有人可以确认这一点吗,那么我会将其标记为正确答案
1赞 charlag 12/14/2018
它有效,只是尝试并逃脱了小鸡蛋问题!谢谢