FingerprintManager.authenticate() 期间出现 UserNotAuthenticatedException
UserNotAuthenticatedException during FingerprintManager.authenticate()
我在 Android KeyStore 中存储了一个加密密码。
我想通过使用指纹对用户进行身份验证来解密该密码 API。
据我了解,我必须调用FingerprintManager.authenticate(CryptoObject 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, ...);
方法 getDecryptionCipher()
在 cipher.init()
调用之前可以正常工作。在此调用中,我得到一个 UserNotAuthenticatedException
,因为用户未针对此 secretKey 进行身份验证。这在某种程度上是有道理的。但这不是一个循环,无法实现吗:
- 为了验证用户,我想使用his/her指纹
- 要侦听 his/her 指纹,我需要初始化密码,在 return 中需要经过身份验证的用户
这是怎么回事??
编辑:
我使用模拟器(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);
}
}
事实证明,该问题与 KeyGenParameterSpec
的一个已知问题有关,该问题会阻止在未经身份验证的情况下使用 public 密钥(这正是 public 密钥的作用不需要)。
可以在这里找到相关的 question/answer:
解决方法是从最初创建的密钥创建一个 PublicKey
并使用这个不受限制的公钥来初始化密码。
所以我的最终密码使用 AES/CBC/PKCS7Padding 并通过这种方法初始化:
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);
}
我找到了逃脱第 22 条军规的方法!
你这样做:
你try{
.init
你Cipher
一如既往
如果没有 UserNotAuthenticatedException
(因为用户在您的密钥有效期内通过了身份验证,即因为他在几秒钟前解锁了他的设备)然后执行您的 encrypt/decrypt 例程。结束!
您使用 null
(是!)CryptoObject
捕获了 UserNotAuthenticatedException
- 运行 FingerprintManager.authenticate
工作流程,然后在 onAuthenticationSucceeded
再次回调初始化你的密码(是的!),但这次它不会抛出 UserNotAuthenticatedException
并将这个初始化实例用于 encrypt/decrypt (回调 returns null
作为我们用 null
CryptoObject 调用它,所以我们不能使用它)。结束!
就这么简单...
但是我摸索了两天才找到这个方法。更不用说 - 似乎在线提供的所有身份验证示例都是错误的!
我在 Android KeyStore 中存储了一个加密密码。
我想通过使用指纹对用户进行身份验证来解密该密码 API。
据我了解,我必须调用FingerprintManager.authenticate(CryptoObject 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, ...);
方法 getDecryptionCipher()
在 cipher.init()
调用之前可以正常工作。在此调用中,我得到一个 UserNotAuthenticatedException
,因为用户未针对此 secretKey 进行身份验证。这在某种程度上是有道理的。但这不是一个循环,无法实现吗:
- 为了验证用户,我想使用his/her指纹
- 要侦听 his/her 指纹,我需要初始化密码,在 return 中需要经过身份验证的用户
这是怎么回事??
编辑:
我使用模拟器(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);
}
}
事实证明,该问题与 KeyGenParameterSpec
的一个已知问题有关,该问题会阻止在未经身份验证的情况下使用 public 密钥(这正是 public 密钥的作用不需要)。
可以在这里找到相关的 question/answer:
解决方法是从最初创建的密钥创建一个 PublicKey
并使用这个不受限制的公钥来初始化密码。
所以我的最终密码使用 AES/CBC/PKCS7Padding 并通过这种方法初始化:
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);
}
我找到了逃脱第 22 条军规的方法!
你这样做:
你try{
.init
你Cipher
一如既往
如果没有
UserNotAuthenticatedException
(因为用户在您的密钥有效期内通过了身份验证,即因为他在几秒钟前解锁了他的设备)然后执行您的 encrypt/decrypt 例程。结束!您使用
null
(是!)CryptoObject
捕获了UserNotAuthenticatedException
- 运行FingerprintManager.authenticate
工作流程,然后在onAuthenticationSucceeded
再次回调初始化你的密码(是的!),但这次它不会抛出UserNotAuthenticatedException
并将这个初始化实例用于 encrypt/decrypt (回调 returnsnull
作为我们用null
CryptoObject 调用它,所以我们不能使用它)。结束!
就这么简单...
但是我摸索了两天才找到这个方法。更不用说 - 似乎在线提供的所有身份验证示例都是错误的!