Android指纹登录/指纹支付简述

一、简述

业务需求,需要指纹登录,鉴于市面上的资料不是特别齐全,走了不少弯路。现在通了,写点东西给大伙做个参考。末尾会提供demo和参考资料

二、指纹登录/支付工作流程

指纹验证加密流程.png

最新的流程图请点击链接

三、原理解析

  1. 指纹验证
    通过FingerprintManager.authenticate()方法即可验证,同时实现FingerprintManager.AuthenticationCallback即可进行监听。
  2. 数据加解密
    • 待加密数据结合Android KeyStore System进行加密,需要经过设备校验后才能解密。(Android M后才有)
    • 指纹验证成功后,回调AuthenticationCallback.onAuthenticationSucceeded(AuthenticationResult result),利用result中的CryptoObject的Cipher进行加密
      Cipher cipher = result.getCryptoObject().getCipher();
      byte[] encrypted = cipher.doFinal(data.getBytes());
      byte[] IV = cipher.getIV();
      String se = Base64.encodeToString(encrypted, Base64.URL_SAFE);
      String siv = Base64.encodeToString(IV, Base64.URL_SAFE);
    
    • 获得加密的数据和iv后,本地保存起来。需要用的时候,再根据SecretKey、iv初始化出cipher,进行解密cipher.doFinal(byte[] input)
    • 为什么这样安全? --> 因为Cipher根据SecretKey和IV初始化出来,而SecretKey由KeyStore保存在Android系统中,IV由指纹验证自动生成。由这两者生成出来的Cipher进行加解密,即可保证安全。

四、实现过程(以指纹登录为例,指纹支付也大同小异)

  1. 判断当前设备是否支持指纹(具体详细代码,demo有)
    /**
     * 获取当前设备的指纹状态
     *
     * @param ctx
     * @return 是否支持指纹
     */
    public FingerPrintInitType getFingerprintAvailable(Context ctx) {
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
            return FingerPrintInitType.NOT_SUPPORT;
        } else if (!isKeyProtectedEnforcedBySecureHardware()) {
            return FingerPrintInitType.NO_FINGER_HARDWARE;
        } else if (!manager.isHardwareDetected()) {
            Toast.makeText(ctx, "该设备尚未检测到指纹硬件", Toast.LENGTH_SHORT).show();
            return FingerPrintInitType.NO_FINGER_HARDWARE;
        } else if (!manager.hasEnrolledFingerprints()) {
            Toast.makeText(ctx, "该设备未录入指纹,请去系统->设置中添加指纹", Toast.LENGTH_SHORT).show();
            return FingerPrintInitType.NONE_FINGER;
        }
        return FingerPrintInitType.HAS_FINGER;
    }
  1. 如果当前设备支持指纹,则显示是否开启指纹登录。
    点击开启指纹登录后,则弹出指纹验证UI,并将一些关键数据EncryptData进行加密(如账号、密码/openToken)
    /**
     * 指纹验证关键方法
     *
     * @param intPurpose 加密/解密  KeyProperties.PURPOSE_ENCRYPT/ KeyProperties.PURPOSE_DECRYPT
     * @param encryptData 加密数据,加密的时候才需要填
     */
    public boolean authenticate(int intPurpose, String encryptData) {
        try {
            purpose = intPurpose;
            FingerprintManager.CryptoObject object;
            if (purpose == KeyProperties.PURPOSE_DECRYPT) {
                //解密
                String IV = mLocalSharedPreference.getData(keyAlias + mLocalSharedPreference.IVKeyName);
                object = mLocalAndroidKeyStore.getCryptoObject(keyAlias, Cipher.DECRYPT_MODE, Base64.decode(IV, Base64.URL_SAFE));
                if (object == null) {
                    return false;
                }
            } else {
                //加密
                generateKey();
                data = encryptData;
                object = mLocalAndroidKeyStore.getCryptoObject(keyAlias, Cipher.ENCRYPT_MODE, null);
            }
            mCancellationSignal = new CancellationSignal();
            manager.authenticate(object, mCancellationSignal, 0, this, null);
            return true;
        } catch (SecurityException e) {
            e.printStackTrace();
            return false;
        }
    }
  1. 指纹验证后,会回调FingerprintManager.AuthenticationCallback接口,在这个接口里面进行数据的加密和解密。指纹验证成功会调用public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result),指纹验证失败会调用public void onAuthenticationFailed(),指纹验证失败次数过多会调用public void onAuthenticationError(int errorCode, CharSequence errString)
    @Override
    public void onAuthenticationSucceeded(FingerprintManager.AuthenticationResult result) {
        if (callback == null) {
            return;
        }
        if (result.getCryptoObject() == null) {
            callback.onAuthenticationFail();
            return;
        }
        final Cipher cipher = result.getCryptoObject().getCipher();
        if (purpose == KeyProperties.PURPOSE_DECRYPT) {
            //取出secret key并返回
            String data = mLocalSharedPreference.getData(keyAlias + mLocalSharedPreference.dataKeyName);
            if (TextUtils.isEmpty(data)) {
                callback.onAuthenticationFail();
                return;
            }
            try {
                byte[] decrypted = cipher.doFinal(Base64.decode(data, Base64.URL_SAFE));
                callback.onAuthenticationSucceeded(new String(decrypted));
            } catch (BadPaddingException | IllegalBlockSizeException e) {
                e.printStackTrace();
                callback.onAuthenticationFail();
            }
        } else {
            //将前面生成的data包装成secret key,存入沙盒
            try {
                byte[] encrypted = cipher.doFinal(data.getBytes());
                byte[] IV = cipher.getIV();
                String se = Base64.encodeToString(encrypted, Base64.URL_SAFE);
                String siv = Base64.encodeToString(IV, Base64.URL_SAFE);
                if (mLocalSharedPreference.storeData(keyAlias + mLocalSharedPreference.dataKeyName, se) &&
                        mLocalSharedPreference.storeData(keyAlias + mLocalSharedPreference.IVKeyName, siv)) {
                    callback.onAuthenticationSucceeded(se);
                } else {
                    callback.onAuthenticationFail();
                }
            } catch (BadPaddingException | IllegalBlockSizeException e) {
                e.printStackTrace();
                callback.onAuthenticationFail();
            }
        }
    }

    @Override
    public void onAuthenticationError(int errorCode, CharSequence errString) {
//        LogUtils.d(errorCode + " " + errString);
        if (callback != null) {
            callback.onAuthenticationError();
        }
    }

    @Override
    public void onAuthenticationHelp(int helpCode, CharSequence helpString) {
    }

    @Override
    public void onAuthenticationFailed() {
        if (callback != null) {
            callback.onAuthenticationFail();
        }
    }

备注

  1. 指纹发生变更也会导致指纹验证失败
  2. 指纹验证5次后,30s内再次申请指纹验证会直接失败
  3. Android 6.0(Android M)之后才有统一的指纹验证接口,建议说服产品只适配6.0及之后的设备,之前的放弃
  4. github demo!!!(未有,埋了个坑)

参考资料

  1. android安全存储,使用AndroidKeyStore的问题
  2. Android密钥库系统(需要翻墙)
  3. Fingerprint and payments APIs(需要翻墙)
  4. FingerprintDialog(需要翻墙)
  5. Android KeyStore + FingerprintManager 存储密码(重点感谢)
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。