react-native-mmkv-storage: Unable to use encrypted storage after restoring from iCloud

Describe the bug Either getting or setting in storage with encryption is failing.

For context, this is the wrapper around the library:

import { MMKVLoader } from 'react-native-mmkv-storage';

const SecureStorage = new MMKVLoader()
  .withEncryption()
  .withInstanceID('mySecureStorage')
  .initialize();

export const setItem = (key: string, value: string): void => {
  SecureStorage.setString(key, value);
};

export const getItem = (key: string): string | null | undefined =>
  SecureStorage.getString(key);

and this being the set of actions taken:

SecureStorage.setItem(SOME_KEY, someValue); // this happens and does not throw an error
// ...and even about 1 second later...
SecureStorage.getItem(SOME_KEY); // this does not yield a value

To Reproduce I have no consistent reproduction steps 😅 it’s happening to a lot of people, but only rarely. On devices where this happens, it happens consistently. It’s almost like react-native-mmkv-storage is “broken” for that device.

Expected behavior Setting a value synchronously should allow you to get it right after.

Platform Information:

  • OS: iOS
  • React Native Version 0.68.2
  • Library Version 0.7.6

Additional context On the current device we have that has this issue, a recent restore from iCloud was performed, but it hasn’t been proven that this is the cause.

It’s worth noting that we’re on version 0.7.6 of this library, which gives us the following dependencies in Podfile.lock:

  ...
  - MMKV (1.2.10):
    - MMKVCore (~> 1.2.10)
  - MMKVCore (1.2.13)
  ...

What’s interesting about that is that backup & restore was introduced in MMKV 1.2.11, so our version of MMKVCore would have that functionality, but MMKV does not. Is that an issue?

About this issue

  • Original URL
  • State: open
  • Created 2 years ago
  • Comments: 29 (9 by maintainers)

Most upvoted comments

I agree. There should be a way to work around this. I will look into it. The simplest way would be to exclude mmkv files from cloud backup and throw some error upon which store can be deleted and recreated possibly.

What about verifying on app load that the key works with the store, and entirely deleting the store if it does not? That way there’s not an unusable MMKV store

But that would only resolve the issue for folks who choose to sync their keychain, right?

On my iCloud for example, I have “iCloud Backup” set to “on” and “Keychain” set to “off”, so my keychain and my app backups would be out of sync and my app wouldn’t survive a backup and restore.

Even besides getting backup & restore working, I’d like to have the following to at least be able to mitigate it:

  • exceptions thrown when something can’t be set in the store (like this scenario where the encryption key doesn’t match)
  • if an exception does happen because the key we have isn’t valid for the file on disk, entirely remove the storage and re-create it (or provide an API for consumers to do that themselves)

On that second point, the most we can do right now is clearStore, and it doesn’t do enough to resolve the issue

The error today happens because when creating the storage, the library uses this property: kSecAttrAccessibleAfterFirstUnlock

And reading apple docs of this property you have this information: After the first unlock, the data remains accessible until the next restart. This is recommended for items that need to be accessed by background applications. Items with this attribute migrate to a new device when using encrypted backups.

What happens is:

  • When using withEncryption, the app adds the data to the keychain, with a property saying that the data will be backed up to icloud.
  • However, when recovering data from icloud because the keys are encrypted, the library cannot recover the data, and also cannot change them, in this case the user accesses the app, but does not recover the old data, much less manages to update the new ones, since the lib doesn’t have a treatment for this scenario, it simply doesn’t save the data and that’s it.

There are two solutions:

  1. When initializing the storage pass the property saying not to save the data in icloud, like kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly
setAccessibleIOS(IOSAccessibleStates.AFTER_FIRST_UNLOCK_THIS_DEVICE_ONLY)
or
setAccessibleIOS('AccessibleAfterFirstUnlockThisDeviceOnly')
  1. Use a custom key when creating the storage, so the storage will be encrypted with your custom key, so it will be possible to recover the keys correctly after a backup.
encryptWithCustomKey('your-custom-key')

Unfortunately the documentation is not clear enough, and this error is “ignored” by the library, so if you use encryption you must follow one of these two steps.

@ammarahm-ed , @abejfehr , any solution yet?