如何要求用户身份验证仅用于解密而不是加密

dug*_*ous 5 android android-keystore

我在 AndroidKeyStore 中有一个公钥/私钥对,生成如下:

val spec = KeyGenParameterSpec.Builder(alias(username), KeyProperties.PURPOSE_DECRYPT or KeyProperties.PURPOSE_ENCRYPT)
                .setKeySize(keySize)
                .setUserAuthenticationRequired(true)
                .setBlockModes(ablockMode)
                .setEncryptionPaddings(apaddingMode)
                .setCertificateSubject(X500Principal("CN=Itsami Mario, OU=Adventure Unit, O=Plumber Bros, C=US"))
                .setKeyValidityStart(Date())
                .setKeyValidityEnd(Date(Date().time + 1000 * 60 * 60 * 24 * 7))
                .setCertificateSerialNumber(BigInteger(64, SecureRandom()))
                .setDigests(digest)
                .build()

        keyPairGen.initialize(spec)
        return keyPairGen.genKeyPair()
Run Code Online (Sandbox Code Playgroud)

我希望每次使用私钥时都需要生物识别身份验证,但我不想在使用公钥加密时要求生物识别提示。但是,当我在 KeyGenerator 中使用setUserAuthenticationRequired(true),然后在不首先显示 BiometricPrompt 的情况下尝试加密时,我收到一条android.security.KeyStoreException消息:Key user not authenticated

如何要求解密验证而不加密加密

div*_*eek 5

您必须在运行 Android 6 Marshmallow 的设备上进行测试。这是该版本中的一个已知问题,已在 Android 7 中修复。

要解决此问题,您可以提取公钥的编码并PublicKey从中创建一个新对象,如下所示:

PublicKey publicKey = keyPair.getPublicKey();
PublicKey unrestrictedPublicKey =
         KeyFactory.getInstance(publicKey.getAlgorithm()).generatePublic(
                 new X509EncodedKeySpec(publicKey.getEncoded()));
Run Code Online (Sandbox Code Playgroud)

这适用于所有版本。

请注意,还可以创建在解密时需要身份验证但在加密时不需要身份验证的 AES 密钥,这非常酷(AES 比 RSA 快得多)。诀窍是在 AndroidKeyStore 之外生成密钥,然后将其导入两次,一次使用,PURPOSE_ENCRYPT一次使用PURPOSE_DECRYPT,使用两个不同的别名,并在 DECRYPT 版本上指定用户身份验证要求。就像是:

// Note that we do *not* specify "AndroidKeyStore" when we call getInstance()
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
keyGen.init(128);
SecretKey secretKey = keyGen.generateKey();

// This time we do specify "AndroidKeyStore".
KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null);

// Now we import the encryption key, with no authentication requirements.
keyStore.setEntry(
     "encrypt_key",
     new KeyStore.SecretKeyEntry(secretKey),
     new KeyProtection.Builder(KeyProperties.PURPOSE_ENCRYPT)
             .setBlockMode(KeyProperties.BLOCK_MODE_GCM)
             .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
             .build());

// And the decryption key, this time requiring user authentication.
keyStore.setEntry(
     "decrypt_key",
     new KeyStore.SecretKeyEntry(secretKey),
     new KeyProtection.Builder(KeyProperties.PURPOSE_DECRYPT)
             .setBlockMode(KeyProperties.BLOCK_MODE_GCM)
             .setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_NONE)
             .setUserAuthentication(true)
             .build());
Run Code Online (Sandbox Code Playgroud)

现在,您可以随时使用密钥别名“encrypt_key”进行加密,无需用户身份验证,并且可以使用密钥别名“decrypt_key”进行解密,但前提是您执行该操作BiometricPrompt

这样做的缺点是秘密会短暂存在于非安全内存中。实际上,只有当攻击者在创建密钥时已经破坏了设备时,这才重要,在这种情况下,您很可能已经丢失了。