Когда мы звоним
mFingerprintManager
.authenticate(cryptoObject, 0 /* flags */, mCancellationSignal, this, null);
, я замечаю, что вполне нормально передать null
для cryptoObject
.Согласно документации FingerprintManager
FingerprintManager.CryptoObject: объект, связанный с вызовом, или ноль, если ничего не требуется.
Согласно https://github.com/googlesamples/android-FingerprintDialog, это показывает длинный шаг для создания CryptoObject
.
Так что я не уверен, должен ли я использовать CryptoObject
или null
для моего варианта использования.Я прочитал Зачем нужен криптообъект для аутентификации по отпечатку пальца Android? , но все еще не могу полностью понять и принять решение для моего случая.
Мой пример использования следующий:
У меня есть экран блокировки запуска приложения для заметок.Обычно, когда пользователь включает экран блокировки при запуске, ему необходимо настроить рисунок шаблона.В случае, если он забудет свой рисунок рисунка, он может использовать свой отпечаток пальца в качестве альтернативы.Приложение выглядит следующим образом
![enter image description here](https://i.stack.imgur.com/Gehdw.png)
Это исходный код.В настоящее время я использую CryptoObject
.Однако, согласно отзывам моих пользователей, меньшинство из них сталкивается с проблемой приложения для этой новой функции.Хотя мы не видим отчетов о сбоях в консоли Google Play, мы подозреваем, что что-то пошло не так во время генерации CryptoObject
.
Итак, если CryptoObject
можно заменить на ноль, мы с радостью сделаем это, чтобы упростить наш код.
Нужен ли мне объект CryptoObject или нуль для следующего использованиядело во время FingerprintManager.authenticate
/**
* Small helper class to manage text/icon around fingerprint authentication UI.
*/
public class FingerprintUiHelper extends FingerprintManagerCompat.AuthenticationCallback {
private static final String TAG = "FingerprintUiHelper";
private static final String ANDROID_KEY_STORE = "AndroidKeyStore";
private static final String DEFAULT_KEY_NAME = "hello world key name";
private int configShortAnimTime;
private final FingerprintManagerCompat mFingerprintManager;
private final ImageView mIcon;
private final Callback mCallback;
private CancellationSignal mCancellationSignal;
private boolean mSelfCancelled;
private final ResetErrorRunnable resetErrorRunnable = new ResetErrorRunnable();
private final int mSuccessColor;
private final int mAlertColor;
private class ResetErrorRunnable implements Runnable {
@Override
public void run() {
resetError();
}
}
public static FingerprintUiHelper newInstance(ImageView icon, Callback callback, int successColor, int alertColor) {
FingerprintManagerCompat fingerprintManagerCompat = FingerprintManagerCompat.from(WeNoteApplication.instance());
return new FingerprintUiHelper(fingerprintManagerCompat, icon, callback, successColor, alertColor);
}
private void initResource() {
configShortAnimTime = WeNoteApplication.instance().getResources().getInteger(android.R.integer.config_shortAnimTime);
}
/**
* Constructor for {@link FingerprintUiHelper}.
*/
private FingerprintUiHelper(FingerprintManagerCompat fingerprintManager,
ImageView icon, Callback callback, int successColor, int alertColor) {
initResource();
mFingerprintManager = fingerprintManager;
mIcon = icon;
mCallback = callback;
mSuccessColor = successColor;
mAlertColor = alertColor;
}
public boolean isFingerprintAuthAvailable() {
// The line below prevents the false positive inspection from Android Studio
// noinspection ResourceType
return mFingerprintManager.isHardwareDetected()
&& mFingerprintManager.hasEnrolledFingerprints();
}
/**
* Initialize the {@link Cipher} instance with the created key in the
* {@link #createKey(String, boolean)} method.
*
* @param keyName the key name to init the cipher
* @return {@code true} if initialization is successful, {@code false} if the lock screen has
* been disabled or reset after the key was generated, or if a fingerprint got enrolled after
* the key was generated.
*/
private boolean initCipher(Cipher cipher, String keyName) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
return false;
}
KeyStore keyStore;
KeyGenerator keyGenerator;
try {
keyStore = KeyStore.getInstance(ANDROID_KEY_STORE);
} catch (KeyStoreException e) {
Log.e(TAG, "", e);
return false;
}
try {
keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, ANDROID_KEY_STORE);
} catch (NoSuchAlgorithmException | NoSuchProviderException e) {
Log.e(TAG, "", e);
return false;
}
// The enrolling flow for fingerprint. This is where you ask the user to set up fingerprint
// for your flow. Use of keys is necessary if you need to know if the set of
// enrolled fingerprints has changed.
try {
keyStore.load(null);
// Set the alias of the entry in Android KeyStore where the key will appear
// and the constrains (purposes) in the constructor of the Builder
KeyGenParameterSpec.Builder builder = new KeyGenParameterSpec.Builder(keyName,
KeyProperties.PURPOSE_ENCRYPT |
KeyProperties.PURPOSE_DECRYPT)
.setBlockModes(KeyProperties.BLOCK_MODE_CBC)
// Require the user to authenticate with a fingerprint to authorize every use
// of the key
.setUserAuthenticationRequired(true)
.setEncryptionPaddings(KeyProperties.ENCRYPTION_PADDING_PKCS7);
// This is a workaround to avoid crashes on devices whose API level is < 24
// because KeyGenParameterSpec.Builder#setInvalidatedByBiometricEnrollment is only
// visible on API level +24.
// Ideally there should be a compat library for KeyGenParameterSpec.Builder but
// which isn't available yet.
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
builder.setInvalidatedByBiometricEnrollment(true);
}
keyGenerator.init(builder.build());
keyGenerator.generateKey();
} catch (NoSuchAlgorithmException | InvalidAlgorithmParameterException
| CertificateException | IOException e) {
Log.e(TAG, "", e);
return false;
}
try {
keyStore.load(null);
SecretKey key = (SecretKey) keyStore.getKey(keyName, null);
cipher.init(Cipher.ENCRYPT_MODE, key);
return true;
} catch (Exception e) {
Log.e(TAG, "", e);
return false;
}
}
public void startListening() {
if (!isFingerprintAuthAvailable()) {
return;
}
Cipher defaultCipher;
try {
defaultCipher = Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/"
+ KeyProperties.BLOCK_MODE_CBC + "/"
+ KeyProperties.ENCRYPTION_PADDING_PKCS7);
} catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
Log.e(TAG, "", e);
return;
}
if (false == initCipher(defaultCipher, DEFAULT_KEY_NAME)) {
return;
}
FingerprintManagerCompat.CryptoObject cryptoObject = new FingerprintManagerCompat.CryptoObject(defaultCipher);
startListening(cryptoObject);
showIcon();
}
private void startListening(FingerprintManagerCompat.CryptoObject cryptoObject) {
if (!isFingerprintAuthAvailable()) {
return;
}
mCancellationSignal = new CancellationSignal();
mSelfCancelled = false;
// The line below prevents the false positive inspection from Android Studio
// noinspection ResourceType
mFingerprintManager
.authenticate(cryptoObject, 0 /* flags */, mCancellationSignal, this, null);
}
public void stopListening() {
if (mCancellationSignal != null) {
mSelfCancelled = true;
mCancellationSignal.cancel();
mCancellationSignal = null;
}
}
@Override
public void onAuthenticationError(int errMsgId, CharSequence errString) {
if (!mSelfCancelled) {
if (errMsgId == FingerprintManager.FINGERPRINT_ERROR_LOCKOUT) {
mIcon.removeCallbacks(resetErrorRunnable);
showError();
return;
}
if (errMsgId == FingerprintManager.FINGERPRINT_ACQUIRED_TOO_FAST) {
return;
}
showError();
mIcon.postDelayed(resetErrorRunnable, configShortAnimTime);
}
}
@Override
public void onAuthenticationHelp(int helpMsgId, CharSequence helpString) {
showError();
mIcon.postDelayed(resetErrorRunnable, configShortAnimTime);
}
@Override
public void onAuthenticationFailed() {
showError();
mIcon.postDelayed(resetErrorRunnable, configShortAnimTime);
}
@Override
public void onAuthenticationSucceeded(FingerprintManagerCompat.AuthenticationResult result) {
mIcon.setColorFilter(mSuccessColor);
mIcon.postDelayed(() -> mCallback.onAuthenticated(), configShortAnimTime);
}
private void showIcon() {
mIcon.setVisibility(View.VISIBLE);
}
private void showError() {
mIcon.setColorFilter(mAlertColor);
}
private void resetError() {
mIcon.clearColorFilter();
}
public interface Callback {
void onAuthenticated();
}
}