fix(android): make decrypt side-effect-free and return false for stale/missing keys#773
Open
jeanregisser wants to merge 1 commit intooblador:masterfrom
Conversation
…e/missing keys - Avoid creating Keystore keys during decrypt across AES-GCM, AES-CBC, RSA by introducing `getExistingKeyOrNull` in `CipherStorageBase` and using it in decrypt paths. - In `KeychainModule.getGenericPassword`, resolve `false` when the Keystore key is missing or decryption fails with tag/padding errors (e.g., `AEADBadTagException`, `BadPaddingException`), preserving explicit errors for other cases. - Add inline comments documenting the reinstall + `allowBackup=true` scenario and the rationale for avoiding side effects on reads. - Improves predictability after app reinstalls: listing no longer changes as a side effect of reads, and stale entries are treated gracefully. Potential impact - Calls that previously rejected on AEAD/BadPadding (or after an implicit key creation on read) now resolve `false`. Apps relying on those specific rejections should adjust handling.
1 task
github-merge-queue bot
pushed a commit
to valora-xyz/wallet-stack
that referenced
this pull request
Sep 29, 2025
### Description Use latest version of `@divvi/react-native-keychain` which contains oblador/react-native-keychain#773 It's published from https://github.com/mobilestack-xyz/react-native-keychain/commits/divvi-fork/ Note: I've chosen this versioning scheme to denote it's react-native-keychain version 10.0.0 + divvi changes ### Test plan - See oblador/react-native-keychain#773 ### Related issues - Part of ENG-636 ### Backwards compatibility Yes ### Network scalability If a new NetworkId and/or Network are added in the future, the changes in this PR will: - [x] Continue to work without code changes, OR trigger a compilation error (guaranteeing we find it when a new network is added)
github-merge-queue bot
pushed a commit
to valora-xyz/wallet-stack
that referenced
this pull request
Sep 29, 2025
### Description Use latest version of `@divvi/react-native-keychain` which contains oblador/react-native-keychain#773 It's published from https://github.com/mobilestack-xyz/react-native-keychain/commits/divvi-fork/ Note: I've chosen this versioning scheme to denote it's react-native-keychain version 10.0.0 + divvi changes ### Test plan - See oblador/react-native-keychain#773 ### Related issues - Part of ENG-636 ### Backwards compatibility Yes ### Network scalability If a new NetworkId and/or Network are added in the future, the changes in this PR will: - [x] Continue to work without code changes, OR trigger a compilation error (guaranteeing we find it when a new network is added)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fix annoying backup/restore behavior on Android
The problem that drove us crazy
You know that moment when your app gets reinstalled and suddenly the keychain acts weird? Here's what was happening:
allowBackup="true"→RN_KEYCHAINpreferences get restored, but the actual encryption keys are gonegetAllGenericPasswordServices()suddenly shows that entry existsBasically, just trying to read an entry would change what the listing API returned. That's not supposed to happen.
What we tried first
Setting
allowBackup="false"works around this by not restoring the preferences in the first place. But lots of apps already ship with backups enabled and can't easily change that.The real fix
We made two key changes:
1. Stop creating keys during reads
2. Handle the "stale data" case gracefully
false(not found) instead of throwingfalseinstead of throwingWhy this helps everyone
falseinstead of erroring outCryptoFailedExceptionerrors, often caused by the same underlying backup/restore mismatchThe trade-off
We're now treating some decryption failures (like authentication tag mismatches) as "not found" instead of explicit errors. In 99% of cases this is what you want after a backup restore. In rare cases where you really need to know if data was corrupted vs just stale, you might miss that signal.
We added clear warning logs when this happens, and we could add a "strict mode" later if needed.
How to test
Quick test:
allowBackup="true")getAllGenericPasswordServices()→ should be empty and stay emptyfalsecleanlygetAllGenericPasswordServices()again → should still be empty (this is the key improvement!)Before this fix, step 5 would suddenly show the entry you tried to read in step 4, even though it couldn't actually be decrypted.
Manual backup simulation (for deeper testing):
You can manually simulate the backup/restore scenario using Android Studio's Device Explorer:
/data/data/com.yourapp/files/datastore/RN_KEYCHAIN.preferences_pbto your computerRN_KEYCHAIN.preferences_pbfile back to the same location (simulates backup restore - prefs restored, Keystore still empty)falsewithout mutating the KeystoreBottom line
Your keychain will now behave predictably after backup/restore, even if you can't change your backup settings. Reading entries won't have weird side effects anymore, and you'll see fewer random crypto exceptions in production.
Other inconsistency spotted (to address separately)
Note: There's still an inconsistency where
hasGenericPasswordchecks preferences (stored) whilegetAllGenericPasswordServiceschecks Keystore (decryptable). This PR reduces the confusion by preventing reads from mutating Keystore state, but we should consider fixing that too.