Skip to content

feat: store source credentials in secure storage - #3181

Open
ethicnology wants to merge 1 commit into
ImranR98:mainfrom
ethicnology:secure-credentials-storage
Open

feat: store source credentials in secure storage#3181
ethicnology wants to merge 1 commit into
ImranR98:mainfrom
ethicnology:secure-credentials-storage

Conversation

@ethicnology

Copy link
Copy Markdown
Contributor

Source credentials (github-creds, gitlab-creds — personal access
tokens) are stored in cleartext in SharedPreferences
(/data/data/<pkg>/shared_prefs/*.xml), readable by anything with root or
backup access to the device.

What this PR changes

  • Credentials (any setting key ending in -creds) now live in
    flutter_secure_storage (Android Keystore-backed) instead of
    SharedPreferences.
  • Migration is automatic and two-layered: an eager sweep of existing
    -creds keys runs at settings initialization, and getCredential also
    migrates lazily on first read as a fallback. No user action needed;
    tokens keep working.
  • Reads/writes go through getSettingStringOrCredential /
    setSettingStringOrCredential, which route -creds keys to secure
    storage and everything else to SharedPreferences as before (settings
    page form and source config resolution updated accordingly).
  • Writing an empty value deletes the credential; no cleartext copy is ever
    left in SharedPreferences.

Side effect on exports: since credentials no longer live in
SharedPreferences, "include settings: all" exports no longer contain them
at all. After a restore, credentials need to be re-entered — which is the
safer behavior anyway (backups shouldn't carry tokens).

What it deliberately does NOT change

  • No change to which sources support credentials or how they're used in
    requests. No new permissions. flutter_secure_storage 10.x requires
    minSdk 23; Obtainium already requires 26, so no manifest change.

@master-bob

master-bob commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Hi, this looks really neat and a beneficial feature!

I had two further questions for you:

  • Would it be beneficial to upgrade to flutter_secure_storage version 11.0.0 (brings minSdk to 24)?
  • How relevant is the InvalidKeyException which can occur with Android AutoBackup[1]?

And a general question:

  • How acceptable is the default Android encryption (RSA OAEP + AES-GCM)?

Thaanks for the dev work!! Good luck! 👍


  1. See the bottom of docs.

@ImranR98

Copy link
Copy Markdown
Owner

Could you confirm that this doesn't break background update checks? I tried something like this once before, and at least with my method it wouldn't work in the background.

@ethicnology
ethicnology force-pushed the secure-credentials-storage branch from c253eda to b8b2b48 Compare August 13, 2026 13:50
@ethicnology

Copy link
Copy Markdown
Contributor Author

Thanks both for the feedback.

Quick context: we audit infrastructure and apps that interact with BULL Wallet. Since some users install and track BULL Wallet through Obtainium, we also ran a Kimi K3-assisted review of Obtainium.

  • flutter_secure_storage 11: I’ve updated the PR to flutter_secure_storage 11.0.0. Obtainium already requires API 26, compiles against API 37, and uses Flutter 3.44, so v11’s API 24 minimum does not reduce compatibility. The v10 migration warning does not apply here because Obtainium has never shipped secure storage; this PR migrates existing cleartext keys directly into a new v11 store.

  • Auto Backup: That concern is valid. SharedPreferences can be restored, while Android Keystore keys are device-bound and non-exportable, so restored ciphertext could fail with InvalidKeyException. I added targeted backup/data-extraction rules for the plugin’s preference files only; other Obtainium settings remain backed up. After a restore, users will need to re-enter source credentials, matching this PR’s export behavior.

  • Crypto: The default path uses AES-128-GCM with a fresh 96-bit IV and 128-bit tag, and wraps the AES key with Android Keystore RSA-OAEP. That is consistent with current Android, OWASP, and NIST guidance for this use case, without assuming StrongBox/TEE support on every device.

  • Background checks: I deliberately used the non-biometric constructor because WorkManager needs to read tokens without user interaction. The callback initializes Flutter and awaits SettingsProvider.initializeSettings() before sources read credentials, and the migration tests pass with v11. I have not yet run an on-device WorkManager job with a migrated token, so I do not want to overclaim that runtime path yet.

References: v11 changelog, Android backup, Android crypto, Android Keystore, OWASP MASWE-0006, NIST GCM, NIST RSA.

@master-bob

master-bob commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Nice work with the backups!

I was curious about the 'flutter_test' changes in the pubspec.* files, so I investigated a bit. Given that the Dev Guide lists flutter tests as non-existent in this project, I would think that the flutter_test changes in the pubspec.* files are out of scope for this feature?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants