android .security.KeyStoreException: Подпись / MA C Не удалось проверить при попытке расшифровать в React Native Module - PullRequest
0 голосов
/ 18 марта 2020

Я столкнулся со странной проблемой, которая начала возникать после недавнего обновления Android на моем телефоне Galaxy S8.

У меня есть модуль 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);
        }
    }
}

В какой-то момент я думал, что это из-за преобразования между байтом [] и строкой, но это не проблема.

1 Ответ

0 голосов
/ 19 марта 2020

Итак, через несколько часов я разобрался с проблемами, и теперь код работает.

Вот рабочий код:

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