android - 在 FingerprintManager.authenticate 期间,我是否需要 CryptoObject 对象或 null 对于以下用例

标签 android security fingerprint android-fingerprint-api

当我们打电话时

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

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

FingerprintManager.CryptoObject: object associated with the call or null if none required.


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


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

我的用例如下。

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

enter image description here


这是源代码。目前,我正在使用 CryptoObject。然而,根据我的用户反馈,他们中的一小部分人面临着这个新功能的一些应用程序问题。尽管我们在 Google Play Console 中没有看到任何崩溃报告,但我们怀疑在 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 key 对,并与服务器共享公钥。

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

与仅仅说“指纹身份验证成功”相比,这增加了额外的安全级别,因为 - 除非设备上存在一些严重的安全缺陷 - 私钥在用户经过身份验证之前无法使用,即使在已取得 root 权限的设备上也是如此。

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

相关文章:

ios - 如何在 Xamarin 中处理 iOS 生物识别身份验证中的错误情况?

java - 帮助弹出菜单

android - 当 Firebase 有新条目时如何向客户端推送通知?

javascript - Android 2.3 浏览器中的固定定位应该可以工作……不是吗?

linux - 如何在 Linux 上为 IdentityServer4 配置 key

mysql - nodejs/socket.io 与 node-mysql |安全困惑

fingerprint - 将生物指纹设备的数据保存到数据库-考勤系统

java - Android 设备重启后如何启动我的应用程序进程?

security - GWT RPC - 它是否足以防范 CSRF?

opencv - 指纹图像中的感兴趣区域