安卓指纹识别 - 密码原语不受AndroidKeyStore提供程序支持

7
我正在尝试将Android指纹功能集成到一个样例应用程序中。 使用的密码未被识别为有效,但我不知道为什么,因为根据Android文档,它应该得到支持。
密码是建立在:
return Cipher.getInstance(KeyProperties.KEY_ALGORITHM_RSA + "/" +KeyProperties.BLOCK_MODE_ECB + "/" + KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1);

这个密码算法在官方文档中列出
随后使用的keyGenerator和keyFactory是如下生成的。
            keyStore = KeyStore.getInstance("AndroidKeyStore");
            keyStore.load(null); // Ensure the key store can be loaded before continuing.

            keyGenerator = KeyPairGenerator.getInstance(KeyProperties.KEY_ALGORITHM_RSA, "AndroidKeyStore");
            keyFactory = KeyFactory.getInstance("RSA");

            createCipher(); // If this doesn't throw, the cipher we need is available.

我也使用那个加密器初始化了密钥生成器:
 keyGenerator.initialize(new KeyGenParameterSpec.Builder(keyAlias,
                    KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT) //
                    .setBlockModes(KeyProperties.BLOCK_MODE_ECB) //
                    .setUserAuthenticationRequired(true) //
                    .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1) //
                    .build());

            keyGenerator.generateKeyPair();

我在加密过程中也添加了公钥,而公钥是这样生成的:
private PublicKey getPublicKey() throws GeneralSecurityException {
    PublicKey publicKey = keyStore.getCertificate(keyAlias).getPublicKey();
    KeySpec spec = new X509EncodedKeySpec(publicKey.getEncoded());
    return keyFactory.generatePublic(spec);
}

编辑:添加了私钥部分:
PrivateKey getPrivateKey() throws GeneralSecurityException {
    return (PrivateKey) keyStore.getKey(keyAlias, null);
}

实际指纹处理如下:
        Cipher cipher = createCipher();
        cipher.init(Cipher.ENCRYPT_MODE, getPublicKey());
        fingerprintManager.authenticate(new FingerprintManager.CryptoObject(cipher), cancellationSignal,
                0, new FingerprintManager.AuthenticationCallback() {/* cutted */ }, null);

解密:
 cipher = createCipher();
 cipher.init(Cipher.DECRYPT_MODE, getPrivateKey());
 fingerprintManager.authenticate(new FingerprintManager.CryptoObject(cipher), cancellationSignal, 0, new FingerprintManager.AuthenticationCallback() {},  null);

导致以下结果:

进程:com.example.android.fingerprintdialog,PID:16254 java.lang.IllegalArgumentException:加密原语不由AndroidKeyStore提供程序支持:javax.crypto.Cipher@2419dda,spi:com.android.org.conscrypt.OpenSSLCipherRSA$PKCS1@4a4d20b

完整的堆栈跟踪:

04-21 11:48:00.031 16254-16254/com.example.android.fingerprintdialog E/AndroidRuntime: FATAL EXCEPTION: main
                                                                                       Process: com.example.android.fingerprintdialog, PID: 16254
                                                                                       java.lang.IllegalArgumentException: Crypto primitive not backed by AndroidKeyStore provider: javax.crypto.Cipher@2419dda, spi: com.android.org.conscrypt.OpenSSLCipherRSA$PKCS1@4a4d20b
                                                                                           at android.security.keystore.AndroidKeyStoreProvider.getKeyStoreOperationHandle(AndroidKeyStoreProvider.java:160)
                                                                                           at android.hardware.fingerprint.FingerprintManager$CryptoObject.getOpId(FingerprintManager.java:248)
                                                                                           at android.hardware.fingerprint.FingerprintManager.authenticate(FingerprintManager.java:468)
                                                                                           at android.hardware.fingerprint.FingerprintManager.authenticate(FingerprintManager.java:429)
                                                                                           at com.example.android.fingerprintdialog.MainActivity.tryToEncrypt(MainActivity.java:212)
                                                                                           at com.example.android.fingerprintdialog.MainActivity.access$000(MainActivity.java:61)

你能展示一下你调用 KeyPairGenerator.getInstance 的代码吗? - Michael
你的示例代码中提到了使用 getPrivateKey 来初始化 Cipher,但是并没有提供 getPrivateKey 的代码。请提供 getPrivateKey 的代码。 - Alex Klyubin
谢谢Michael和Alex - 我认为我已经添加了必要的细节。如果需要更多细节或者我应该将类上传到github或gist,请告诉我。 - Christof Buechi
你能找到这个问题的解决方案吗? - atasoyh
我已经成功地在Android M和RSA上实现了这个,我认为你不需要使用KeyFactory。看一下这段代码,并调整它以使用RSA https://blog.stylingandroid.com/user-authentication-part-1/ - Android Noob
2个回答

6

我现在也遇到了同样的问题,使用了新的 androidx.biometric。当我尝试执行生物识别加密认证时,我会得到完全相同的错误,例如:

val cipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_RSA + "/"
                + KeyProperties.BLOCK_MODE_ECB + "/"
                + KeyProperties.ENCRYPTION_PADDING_RSA_PKCS1)
cipher.init(Cipher.ENCRYPT_MODE, getPublicKey(KeyFactory.getInstance(KeyProperties.KEY_ALGORITHM_RSA), keyStore))

biometricPrompt.authenticate(promptInfo, BiometricPrompt.CryptoObject(cipher))

getPublicKey 方法和所有其他参数与作者列出的相同。

然后我意识到我们做错了。

我在这个主题中能找到的所有示例都使用带有AES密钥的对称加密。对于这种类型的加密,密钥仅用于加密和解密,因此无论是加密还是解密,它都需要受到生物特征身份验证的保护。这就是为什么在所有示例中,我们看到以下加密提示的代码: biometricPrompt.authenticate(promptInfo, BiometricPrompt.CryptoObject(cipher))

但是对于RSA(也称为非对称)加密,情况是不同的。加密密钥和解密密钥是不同的。加密密钥是私钥,因此不需要以任何方式进行保护。只有解密时需要使用私钥。

这就是为什么我们会得到加密,因为我们试图打开生物识别认证提示来激活公钥,这是无意义的,因为公钥不是秘密。

解决方案非常简单。只需调用没有 CryptoObjectauthenticate 方法 (biometricPrompt.authenticate(promptInfo)),等认证成功后再使用您的公钥进行加密。

希望这可以帮助其他人,因为我找不到任何与这个主题相关的信息,只有经过数小时的思考后,才明白了发生了什么错误。


1

我遇到了相同的异常,在指定Cipher和其他提供程序时我修复了它;例如:

String alg = "AES"; 
Cipher cipher = Cipher.getInstance(alg, "SunJCE");
KeyGenerator generator = KeyGenerator.getInstance(alg, "SunJCE");
SecretKey key = generator.generateKey();
cipher.init(Cipher.ENCRYPT_MODE, key);

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接