【问题标题】:android.security.KeyStoreException: Signature/MAC verification failed When trying to decrypt in a React Native Moduleandroid.security.KeyStoreException:签名/MAC 验证失败尝试在 React Native 模块中解密时
【发布时间】:2020-03-18 14:54:13
【问题描述】:

我遇到了一个奇怪的问题,在我的 Galaxy S8 手机最近一次 Android 更新后开始发生。

我有一个 React Native 模块,用于使用存储在 Android KeyStore 中的密钥对值进行加密,该模块运行良好,但在解密过程中突然开始失败,出现以下异常:

android.security.KeyStoreException: Signature/MAC verification failed

我做了一些调查,发现如果我在加密一个值后立即调用我的解密方法,它工作正常,但如果我尝试在 JS 端的另一个调用中进行解密,它会失败。

此外,如果我提示用户进行生物识别,无论是同一个呼叫还是另一个呼叫,解密都可以正常工作。

这是我的模块代码:

public class BiometricsModule extends ReactContextBaseJavaModule {

    private Promise _promise;
    private ReactApplicationContext _context;
    private String CIPHER_IV = "CIPHER_IV";
    private SettingsStore settingsStore;

    public FVBiometricsModule(@NonNull ReactApplicationContext reactContext) {
        super(reactContext);
        _context = reactContext;
        settingsStore = new SettingsStore(_context);
    }

    @NonNull
    @Override
    public String getName() {
        return "Biometrics";
    }

    @ReactMethod
    public void isEnrolledAsync(final Promise promise) {
        _promise = promise;

        try {
            WritableMap resultData = new WritableNativeMap();

            Integer biometricsCheckResult =  BiometricManager.from(_context).canAuthenticate();
            String reason = parseResult(biometricsCheckResult);

            resultData.putBoolean("result", reason == "SUCCESS");
            if (reason != "SUCCESS") {
                resultData.putString("reason", reason);
            }
            promise.resolve(resultData);
        } catch (Exception e) {
            promise.reject(e);
        }

    }

    @ReactMethod
    public void promptForBiometricsAsync(String prompt, String title, String cancelButtonText, final Promise promise) {
        _promise = promise;
        prompt(null, null, prompt, title, cancelButtonText, false, null);
    }

    @ReactMethod
    public void setPasswordAsync(String username, String password, Boolean biometricsProtected, String prompt, String title, String cancelButtonText, final Promise promise) {
        _promise = promise;
        try {
            generateKey(biometricsProtected);
            Cipher cipher = getCipher(biometricsProtected);
            SecretKey secretKey = getSecretKey();
            cipher.init(Cipher.ENCRYPT_MODE, secretKey);
            settingsStore.setValue(CIPHER_IV, Base64.encodeToString(cipher.getIV(), Base64.DEFAULT));
            if (biometricsProtected) {
                prompt(username, password, prompt, title, cancelButtonText, false, cipher);
            } else {
                encrypt(username, password, cipher);
            }
        } catch (Exception e) {
            promise.reject(e);
        }
    }

    @ReactMethod
    public void getPasswordAsync(String username, Boolean biometricsProtected, String prompt, String title, String cancelButtonText, final Promise promise) {
        _promise = promise;
        try {
            Cipher cipher = getCipher(biometricsProtected);
            SecretKey secretKey = getSecretKey();
            byte[] _iv = Base64.decode(settingsStore.getValue(CIPHER_IV, null), Base64.DEFAULT);
            cipher.init(Cipher.DECRYPT_MODE, secretKey, biometricsProtected ? new IvParameterSpec(_iv) : new GCMParameterSpec(128, _iv));
            if (biometricsProtected) {
                prompt(username, null, prompt, title, cancelButtonText, true, cipher);
            } else {
                decrypt(username, cipher);
            }
        } catch (Exception e) {
            promise.reject(e);
        }
    }

    private String parseResult(Integer biometricsCheckResult) {
        String result = "SUCCESS";
        switch (biometricsCheckResult) {
            case BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE:
                result = "NOT_AVAILABLE";
                break;
            case BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE:
                result = "NOT_AVAILABLE";
                break;
            case BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED:
                result = "NOT_ENROLLED";
                break;
        }
        return result;
    }

    private void generateKey(Boolean biometricsProtected) {
        generateSecretKey(new KeyGenParameterSpec.Builder(
                _context.getPackageName(),
                KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
                .setBlockModes(biometricsProtected ? KeyProperties.BLOCK_MODE_CBC : KeyProperties.BLOCK_MODE_GCM)
                .setEncryptionPaddings(biometricsProtected ? KeyProperties.ENCRYPTION_PADDING_PKCS7 : KeyProperties.ENCRYPTION_PADDING_NONE)
                .setUserAuthenticationRequired(biometricsProtected)
                .setInvalidatedByBiometricEnrollment(biometricsProtected)
                .build());
    }

    private void generateSecretKey(KeyGenParameterSpec keyGenParameterSpec) {
        try {
            KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore");
            keyGenerator.init(keyGenParameterSpec);
            keyGenerator.generateKey();
        } catch (Exception e) {
            _promise.reject(e);
        }
    }

    private SecretKey getSecretKey() {
        try {
            KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
            keyStore.load(null);
            return ((SecretKey)keyStore.getKey(_context.getPackageName(), null));
        } catch (Exception e) {
            _promise.reject(e);
            return null;
        }
    }

    private Cipher getCipher(Boolean biometricsProtected) {
        try {
            return Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/"
                    + (biometricsProtected ? KeyProperties.BLOCK_MODE_CBC : KeyProperties.BLOCK_MODE_GCM) + "/"
                    + (biometricsProtected ? KeyProperties.ENCRYPTION_PADDING_PKCS7 : KeyProperties.ENCRYPTION_PADDING_NONE));
        } catch (Exception e) {
            _promise.reject(e);
            return null;
        }
    }

    private void prompt(String username, String password, String prompt, String title, String cancelBButtonText, Boolean decrypt, Cipher cipher) {
        MainActivity.mainActivity.runOnUiThread(new Runnable() {
            public void run() {
                WritableMap resultData = new WritableNativeMap();
                Executor _executor = ContextCompat.getMainExecutor(MainActivity.mainActivity);

                BiometricPrompt _biometricPrompt = new BiometricPrompt(MainActivity.mainActivity,
                        _executor, new BiometricPrompt.AuthenticationCallback() {
                    @Override
                    public void onAuthenticationError(int errorCode,
                                                      @NonNull CharSequence errString) {
                        super.onAuthenticationError(errorCode, errString);
                        try {
                            resultData.putBoolean("result", false);
                            _promise.resolve(resultData);
                        } catch (Exception e) {
                            _promise.reject(e);
                        }
                    }

                    @Override
                    public void onAuthenticationSucceeded(
                            @NonNull BiometricPrompt.AuthenticationResult result) {
                        super.onAuthenticationSucceeded(result);
                        try {
                            if (password != null && !decrypt) {
                                byte[] encryptedInfo = result.getCryptoObject().getCipher().doFinal(password.getBytes(Charset.defaultCharset()));
                                settingsStore.setValue(username, Base64.encodeToString(encryptedInfo, Base64.DEFAULT));
                                resultData.putBoolean("result", true);
                            } else if (decrypt) {
                                String decryptedInfo = new String(result.getCryptoObject().getCipher().doFinal(Base64.decode(settingsStore.getValue(username, null), Base64.DEFAULT)));
                                resultData.putString("password", decryptedInfo);
                            }
                            _promise.resolve(resultData);
                        } catch (Exception e) {
                            _promise.reject(e);
                        }
                    }

                    @Override
                    public void onAuthenticationFailed() {
                        super.onAuthenticationFailed();
                        try {
                            resultData.putBoolean("result", false);
                            _promise.resolve(resultData);
                        } catch (Exception e) {
                            _promise.reject(e);
                        }
                    }
                });

                BiometricPrompt.PromptInfo promptInfo = new BiometricPrompt.PromptInfo.Builder()
                        .setTitle(title)
                        .setSubtitle(prompt)
                        .setNegativeButtonText(cancelBButtonText)
                        .setConfirmationRequired(false)
                        .build();

                if (cipher != null)
                    _biometricPrompt.authenticate(promptInfo, new BiometricPrompt.CryptoObject(cipher));
                else
                    _biometricPrompt.authenticate(promptInfo);
            }
        });
    }

    private void encrypt(String username, String password, Cipher cipher) {
        try {
            WritableMap resultData = new WritableNativeMap();
            byte[] encryptedInfo = cipher.doFinal(password.getBytes(Charset.defaultCharset()));
            settingsStore.setValue(username, Base64.encodeToString(encryptedInfo, Base64.DEFAULT));
            resultData.putBoolean("result", true);
            _promise.resolve(resultData);
        } catch (Exception e) {
            _promise.reject(e);
        }
    }

    private void decrypt(String username, Cipher cipher) {
        try {
            WritableMap resultData = new WritableNativeMap();
            String decryptedInfo = new String(cipher.doFinal(Base64.decode(settingsStore.getValue(username, null), Base64.DEFAULT)));
            resultData.putString("password", decryptedInfo);
            _promise.resolve(resultData);
        } catch (Exception e) {
            _promise.reject(e);
        }
    }
}

在某些时候,我虽然是因为 byte[] 和 string 之间的转换,但这不是问题。

【问题讨论】:

    标签: android react-native encryption android-keystore


    【解决方案1】:

    几个小时后我发现了问题,现在代码可以工作了。

    下面是工作代码:

    public class BiometricsModule extends ReactContextBaseJavaModule {
    
        private Promise _promise;
        private ReactApplicationContext _context;
        private SettingsStore settingsStore;
    
        public BiometricsModule(@NonNull ReactApplicationContext reactContext) {
            super(reactContext);
            _context = reactContext;
            settingsStore = new SettingsStore(_context);
        }
    
        @NonNull
        @Override
        public String getName() {
            return "Biometrics";
        }
    
        @ReactMethod
        public void isEnrolledAsync(final Promise promise) {
            _promise = promise;
    
            try {
                WritableMap resultData = new WritableNativeMap();
    
                Integer biometricsCheckResult =  BiometricManager.from(_context).canAuthenticate();
                String reason = parseResult(biometricsCheckResult);
    
                resultData.putBoolean("result", reason == "SUCCESS");
                if (reason != "SUCCESS") {
                    resultData.putString("reason", reason);
                }
                promise.resolve(resultData);
            } catch (Exception e) {
                promise.reject(e);
            }
    
        }
    
        @ReactMethod
        public void promptForBiometricsAsync(String prompt, String title, String cancelButtonText, final Promise promise) {
            _promise = promise;
            prompt(null, null, prompt, title, cancelButtonText, false, null);
        }
    
        @ReactMethod
        public void setPasswordAsync(String username, String password, Boolean biometricsProtected, String prompt, String title, String cancelButtonText, final Promise promise) {
            _promise = promise;
            try {
                Cipher cipher = getCipher(biometricsProtected);
                SecretKey secretKey = getSecretKey(biometricsProtected);
                cipher.init(Cipher.ENCRYPT_MODE, secretKey);
                if (biometricsProtected) {
                    prompt(username, password, prompt, title, cancelButtonText, false, cipher);
                } else {
                    encrypt(username, password, cipher);
                }
            } catch (Exception e) {
                promise.reject(e);
            }
        }
    
        @ReactMethod
        public void getPasswordAsync(String username, Boolean biometricsProtected, String prompt, String title, String cancelButtonText, final Promise promise) {
            _promise = promise;
            try {
                Cipher cipher = getCipher(biometricsProtected);
                SecretKey secretKey = getSecretKey(biometricsProtected);
                byte[] _iv = stringToByteArray(extractIv(settingsStore.getValue(username, null)));
                cipher.init(Cipher.DECRYPT_MODE, secretKey, biometricsProtected ? new IvParameterSpec(_iv) : new GCMParameterSpec(128, _iv));
                if (biometricsProtected) {
                    prompt(username, null, prompt, title, cancelButtonText, true, cipher);
                } else {
                    decrypt(username, cipher);
                }
            } catch (Exception e) {
                promise.reject(e);
            }
        }
    
        private String parseResult(Integer biometricsCheckResult) {
            String result = "SUCCESS";
            switch (biometricsCheckResult) {
                case BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE:
                    result = "NOT_AVAILABLE";
                    break;
                case BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE:
                    result = "NOT_AVAILABLE";
                    break;
                case BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED:
                    result = "NOT_ENROLLED";
                    break;
            }
            return result;
        }
    
        private void generateKey(String alias, Boolean biometricsProtected) {
            generateSecretKey(new KeyGenParameterSpec.Builder(
                    alias,
                    KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT)
                    .setBlockModes(biometricsProtected ? KeyProperties.BLOCK_MODE_CBC : KeyProperties.BLOCK_MODE_GCM)
                    .setEncryptionPaddings(biometricsProtected ? KeyProperties.ENCRYPTION_PADDING_PKCS7 : KeyProperties.ENCRYPTION_PADDING_NONE)
                    .setUserAuthenticationRequired(biometricsProtected)
                    .setInvalidatedByBiometricEnrollment(biometricsProtected)
                    .setKeySize(256)
                    .setRandomizedEncryptionRequired(true)
                    .build());
        }
    
        private void generateSecretKey(KeyGenParameterSpec keyGenParameterSpec) {
            try {
                KeyGenerator keyGenerator = KeyGenerator.getInstance(KeyProperties.KEY_ALGORITHM_AES, "AndroidKeyStore");
                keyGenerator.init(keyGenParameterSpec);
                keyGenerator.generateKey();
            } catch (Exception e) {
                _promise.reject(e);
            }
        }
    
        private SecretKey getSecretKey(Boolean biometricsProtected) {
            try {
                String alias = _context.getPackageName() + "_SECRET_KEY_" + biometricsProtected.toString().toUpperCase();
                KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
                keyStore.load(null);
                if (!keyStore.containsAlias(alias)) {
                    generateKey(alias, biometricsProtected);
                }
                return ((SecretKey)keyStore.getKey(alias, null));
            } catch (Exception e) {
                return null;
            }
        }
    
        private Cipher getCipher(Boolean biometricsProtected) {
            try {
                return Cipher.getInstance(KeyProperties.KEY_ALGORITHM_AES + "/"
                        + (biometricsProtected ? KeyProperties.BLOCK_MODE_CBC : KeyProperties.BLOCK_MODE_GCM) + "/"
                        + (biometricsProtected ? KeyProperties.ENCRYPTION_PADDING_PKCS7 : KeyProperties.ENCRYPTION_PADDING_NONE));
            } catch (Exception e) {
                _promise.reject(e);
                return null;
            }
        }
    
        private void prompt(String username, String password, String prompt, String title, String cancelBButtonText, Boolean decrypt, Cipher cipher) {
            MainActivity.mainActivity.runOnUiThread(new Runnable() {
                public void run() {
                    WritableMap resultData = new WritableNativeMap();
                    Executor _executor = ContextCompat.getMainExecutor(MainActivity.mainActivity);
    
                    BiometricPrompt _biometricPrompt = new BiometricPrompt(MainActivity.mainActivity,
                            _executor, new BiometricPrompt.AuthenticationCallback() {
                        @Override
                        public void onAuthenticationError(int errorCode,
                                                          @NonNull CharSequence errString) {
                            super.onAuthenticationError(errorCode, errString);
                            try {
                                resultData.putBoolean("result", false);
                                _promise.resolve(resultData);
                            } catch (Exception e) {
                                _promise.reject(e);
                            }
                        }
    
                        @Override
                        public void onAuthenticationSucceeded(
                                @NonNull BiometricPrompt.AuthenticationResult result) {
                            super.onAuthenticationSucceeded(result);
                            try {
                                if (password != null && !decrypt) {
                                    byte[] encryptedInfo = result.getCryptoObject().getCipher().doFinal(password.getBytes(Charset.defaultCharset()));
                                    settingsStore.setValue(username, byteArrayToString(encryptedInfo) + "  ||  " + byteArrayToString(result.getCryptoObject().getCipher().getIV()));
                                    resultData.putBoolean("result", true);
                                } else if (decrypt) {
                                    String decryptedInfo = new String(result.getCryptoObject().getCipher().doFinal(stringToByteArray(extractEncryptedContent(settingsStore.getValue(username, null)))));
                                    resultData.putString("password", decryptedInfo);
                                }
                                _promise.resolve(resultData);
                            } catch (Exception e) {
                                _promise.reject(e);
                            }
                        }
    
                        @Override
                        public void onAuthenticationFailed() {
                            super.onAuthenticationFailed();
                            try {
                                resultData.putBoolean("result", false);
                                _promise.resolve(resultData);
                            } catch (Exception e) {
                                _promise.reject(e);
                            }
                        }
                    });
    
                    BiometricPrompt.PromptInfo promptInfo = new BiometricPrompt.PromptInfo.Builder()
                            .setTitle(title)
                            .setSubtitle(prompt)
                            .setNegativeButtonText(cancelBButtonText)
                            .setConfirmationRequired(false)
                            .build();
    
                    if (cipher != null)
                        _biometricPrompt.authenticate(promptInfo, new BiometricPrompt.CryptoObject(cipher));
                    else
                        _biometricPrompt.authenticate(promptInfo);
                }
            });
        }
    
        private void encrypt(String username, String password, Cipher cipher) {
            try {
                WritableMap resultData = new WritableNativeMap();
                byte[] encryptedInfo = cipher.doFinal(password.getBytes(Charset.defaultCharset()));
                settingsStore.setValue(username, byteArrayToString(encryptedInfo) + "  ||  " + byteArrayToString(cipher.getIV()));
                resultData.putBoolean("result", true);
                _promise.resolve(resultData);
            } catch (Exception e) {
                _promise.reject(e);
            }
        }
    
        private void decrypt(String username, Cipher cipher) {
            try {
                WritableMap resultData = new WritableNativeMap();
                String decryptedInfo = new String(cipher.doFinal(stringToByteArray(extractEncryptedContent(settingsStore.getValue(username, null)))));
                resultData.putString("password", decryptedInfo);
                _promise.resolve(resultData);
            } catch (Exception e) {
                _promise.reject(e);
            }
        }
    
        private String byteArrayToString(byte buf[]) {
            return Base64.encodeToString(buf, Base64.DEFAULT);
        }
    
        private byte[] stringToByteArray(String s) {
            return Base64.decode(s, Base64.DEFAULT);
        }
    
        private String extractEncryptedContent(String source) {
            return source.substring(0, source.indexOf(" || ")).trim();
        }
    
        private String extractIv(String source) {
            return source.substring(source.lastIndexOf(" || ") + 4).trim();
        }
    }
    

    【讨论】:

    • 能否请您发布问题的解释和解决方案?仅仅发布源代码并不能帮助读者寻求快速帮助。
    猜你喜欢
    • 2021-07-21
    • 1970-01-01
    • 1970-01-01
    • 2017-12-31
    • 2019-04-15
    • 1970-01-01
    • 2021-06-19
    • 2023-02-25
    • 1970-01-01
    相关资源
    最近更新 更多