当我们打电话

mFingerprintManager
            .authenticate(cryptoObject, 0 /* flags */, mCancellationSignal, this, null);


我注意到将null传递给cryptoObject是完全可以的。根据FingerprintManager documentation


  FingerprintManager.CryptoObject:与呼叫关联的对象,或者
  如果不需要,则为null。




根据https://github.com/googlesamples/android-FingerprintDialog,它显示了创建CryptoObject的漫长步骤。



因此,我不确定我应该使用CryptoObject还是null作为我的用例。我已经读过Why crypto object is needed for Android fingerprint authentication?,但仍然无法完全理解并决定我的情况。

我的用例如下。

我有一个做笔记应用程序的启动锁定屏幕。通常,当用户启用启动锁定屏幕时,他需要设置图案图。万一他忘记了自己的图案绘图,可以使用指纹作为替代。该应用程序如下所示

android - 在FingerprintManager.authenticate中,以下使用情况是否需要CryptoObject对象或null?-LMLPHP



这是源代码。目前,我正在使用CryptoObject。但是,根据我的用户反馈,其中只有少数人面临此新功能的应用程序问题。尽管我们在Google Play控制台中看不到任何崩溃报告,但我们怀疑在生成CryptoObject时出了点问题。

因此,如果CryptoObject可以替换为null,我们很乐意这样做以简化我们的代码。

在FingerprintManager.authenticate中,以下使用情况是否需要CryptoObject对象或null?



/**
 * 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();
    }
}

最佳答案

是否需要CryptoObject取决于是否要执行要求用户使用其指纹进行身份验证的加密操作。只有您知道答案。



例如,假设您的应用程序与服务器通信,并且在某个时候您想向服务器证明用户已使用其应用程序中的指纹进行了身份验证。

您可以执行此操作的方法是,当用户首次在应用程序中“注册”时(无论完成了什么),您将创建一个需要指纹认证的RSA密钥对,并与服务器共享公共密钥。

以后,当您想向服务器证明用户已通过身份验证时,可以要求服务器提供一些数据进行签名。然后,使用RSA私钥创建Signature,并将其包装为CryptoObject。用户通过身份验证后,您可以对从服务器获取的数据进行签名,然后将签名发送到服务器,服务器可以使用公钥验证签名。

这不仅可以说“指纹认证成功”,还可以提高安全性,这是因为-除非设备上存在一些严重的安全漏洞,否则私钥在用户通过身份验证之前是无法使用的,即使在root用户的设备上也是如此。

关于android - 在FingerprintManager.authenticate中,以下使用情况是否需要CryptoObject对象或null?,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/53853910/

10-09 10:05