问题
When we call
mFingerprintManager
.authenticate(cryptoObject, 0 /* flags */, mCancellationSignal, this, null);
I notice that it is completely fine to pass null
for cryptoObject
. According to FingerprintManager documentation
FingerprintManager.CryptoObject: object associated with the call or null if none required.
According to https://github.com/googlesamples/android-FingerprintDialog, it shows a lengthy step to create CryptoObject
.
So, I'm not sure, whether I should use a CryptoObject
, or null
for my use case. I had read Why crypto object is needed for Android fingerprint authentication? but still couldn't completely understand and decide for my case.
My use case is as follow.
I'm having a startup locking screen for a note taking app. Usually, when user enables startup locking screen, he needs to setup pattern drawing. In case he forgets his pattern drawing, he can use his fingerprint as alternative. The app looks as following
This is the source code. Currently, I'm using a CryptoObject
. However, according to my users feedback, a minority of them are facing some app issue for this new feature. Although we don't see any crash reports in Google Play Console, we suspect something went wrong during CryptoObject
generation.
So, if the CryptoObject
can be replaced with null, we would happily do that to simplify our code.
Do I need CryptoObject object, or null for the following use case during 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();
}
}
回答1:
Whether or not you need a CryptoObject
comes down to whether you want to perform a cryptographic operation that requires the user to authenticate with their fingerprint. Only you knows the answer to that.
For example, let's say that your app communicates with a server, and at some point you want to prove to the server that the user has authenticated with their fingerprint in your app.
The way you might go about doing that is that, when the user first "registers" in your app (however that's done), you create an RSA keypair that requires fingerprint authentication, and you share the public key with the server.
Later, when you want to prove to the server that the user has authenticated, you could ask the server for some data to sign. You then create a Signature
with the RSA private key, and wrap that into a CryptoObject
. After the user has authenticated you can sign the data you got from the server, and send the signature to the server which can verify the signature using the public key.
This adds an additional level of security over just saying "the fingerprint authentication succeeded", because - unless there's some critical security flaw on the device - the private key is unusable until the user has authenticated, even on a rooted device.
来源:https://stackoverflow.com/questions/53853910/do-i-need-cryptoobject-object-or-null-for-the-following-use-case-during-fingerp