maxdeviant/redux-persist-transform-encrypt

v4 Not working with react-native

LucasBerger opened this issue · 7 comments

When using version 4.0.0 of this library there are errors when starting the app:

Error: redux-persist-transform-encrypt: Expected outbound state to be a string.
and
Error: Native crypto module could not be used to get secure random number.

Downgrading to 3.x solves these issues

I was worried this would happen.

This is the same issue reported in #49. The "solution" at the time was to downgrade crypto-js to a version that did not depend on Node's built-in crypto.

However, in v4 we upgraded crypto-js to fix some security issues in the previous version (requested in #57).

I anticipated that upgrading crypto-js would break React Native usage, which is why I opted for the major version bump.

Could you see if this workaround listed on the crypto-js repo solves the issue? brix/crypto-js#259 (comment)

I've encountered the same issue. Downgrading is not an options as I was looking to encrypt only partially the redux store, and this is available only on v4.0.0.

What I have done on the other hand is reimplemented the encrypt logic in another file. The only thing I've changed is removed the onError from interface of EncryptTransformConfig.

These are the packages

"react-native": "0.68.0",
"crypto-js": "^4.1.1",
"json-stringify-safe": "^5.0.1",
"redux-persist": "^6.0.0",

This is the implementation - which is pretty much copy paste.

import * as Aes from "crypto-js/aes";
import * as CryptoJsCore from "crypto-js/core";
import stringify from "json-stringify-safe";
import { createTransform } from "redux-persist";
import type { TransformConfig } from "redux-persist/lib/createTransform";

export interface EncryptTransformConfig {
    secretKey: string;
}

const makeError = (message: string) => new Error(`redux-encryption-err: ${message}`);

export const encryptStore = <HSS, S = any, RS = any>(
    config: EncryptTransformConfig,
    transformConfig?: TransformConfig
) => {
    if (typeof config === "undefined") {
        throw makeError("No configuration provided.");
    }

    const { secretKey } = config;
    if (!secretKey) {
        throw makeError("No secret key provided.");
    }

    const onError = console.warn;

    return createTransform<HSS, string, S, RS>(
        (inboundState, _key) => Aes.encrypt(stringify(inboundState), secretKey).toString(),
        (outboundState, _key) => {
            if (typeof outboundState !== "string") {
                return onError(makeError("Expected outbound state to be a string."));
            }

            try {
                const decryptedString = Aes.decrypt(outboundState, secretKey).toString(CryptoJsCore.enc.Utf8);
                if (!decryptedString) {
                    throw new Error("Decrypted string is empty.");
                }

                try {
                    return JSON.parse(decryptedString);
                } catch {
                    return onError(makeError("Failed to parse state as JSON."));
                }
            } catch {
                return onError(
                    makeError("Could not decrypt state. Please verify that you are using the correct secret key.")
                );
            }
        },
        transformConfig
    );
};

Apologies, I'm not sure I'm following how removing the onError from the EncryptTransformConfig resolved the issue.

The error Error: Native crypto module could not be used to get secure random number. seems to stem from React Native not supporting the native crypto package.

@maxdeviant maybe it's not clear. That is just a customisation I've done. It has nothing to do fixing the issue.

I've replicated the logic locally so I have more control over it. Removing the onError was just for me.

Also I've done this as well
brix/crypto-js#259 (comment)

Basically you have to install react-native-get-random-values run pod install and add this import before the crypto-js import (I put in the root index.tsx file), and it seems to work. Now if I comment that import it throws the error, but leaving it uncommented fixes it.

Ah, that makes more sense. Thanks for the clarification!

Downgrading is not an options as I was looking to encrypt only partially the redux store, and this is available only on v4.0.0.

Why do you say this @langarus ? I'm using 3.0.1 and am able to encrypt only part of the store. Maybe I'm missing something. I'm using a nested persist like this:

const rootPersistConfig = {
  key: 'root',
  storage: AsyncStorage,
  blacklist: ['encryptedFields'],
};

const encryptedPersistConfig = {
  key: 'encryptedFields',
  storage: AsyncStorage,
  transforms: [
    encryptTransform({
      secretKey: 'my-super-secret-key',
      onError(error) {},
    }),
  ],
};

const rootReducer = combineReducers({
  offices: officesReducer,
  user: userReducer,
  encryptedFields: persistReducer(
    encryptedPersistConfig,
    encryptedFieldsReducer,
  ),
});

const persistedReducer = persistReducer(rootPersistConfig, rootReducer);

Hi Team

I am also facing the same issue.

I installed the library react-native-get-random-values and import in store.js and sync.js file but still facing the same issue.

simulator_screenshot_E48E912A-77E0-4E04-9ACA-821E73EE047A