Quantcast
Channel: Active questions tagged react-native+android - Stack Overflow
Viewing all articles
Browse latest Browse all 29459

android.security.KeyStoreException: Signature/MAC verification failed When trying to decrypt in a React Native Module

$
0
0

I am facing a weird issue which has started to happen after a recent Android Update on my Galaxy S8 phone.

I have a React Native Module for encrypting values using a key stored in Android KeyStore in which was working perfectly fine and suddenly started to fail with the following exception during decryption:

android.security.KeyStoreException: Signature/MAC verification failed

I did a bit of investigation and figured out that if I call my decrypt method instantly after encrypting a value, it works fine but if I try to do the decryption in another call from the JS side, it fails.

Also if I prompt the user for biometrics, the decryption works fine regardless of being the same call or another call.

Here is my module code:

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);
        }
    }
}

At some point I though it is because of the conversion between byte[] and string but that's not the issue.


Viewing all articles
Browse latest Browse all 29459

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>