Skip to content

PM-40154: feat: Migrate user data to KeystoreEncryptedSharedPreferences - #7158

Open
david-livefront wants to merge 1 commit into
mainfrom
PM-40154-keystore-encrypted-shared-prefs
Open

PM-40154: feat: Migrate user data to KeystoreEncryptedSharedPreferences#7158
david-livefront wants to merge 1 commit into
mainfrom
PM-40154-keystore-encrypted-shared-prefs

Conversation

@david-livefront

@david-livefront david-livefront commented Jul 10, 2026

Copy link
Copy Markdown
Collaborator

🎟️ Tracking

PM-40154

📔 Objective

This PR creates a new KeystoreEncryptedSharedPreferences that utilizes the AndroidKeystore to encrypt the data stored.

Notable changes:

  • This migrates us away from EncryptedSharedPreferences which is deprecated and is not FIPS compliant.
  • The keys are not stored as plaintext.
  • We currently only support the storing of Strings (this is the only type we need at the moment).

@github-actions github-actions Bot added app:password-manager Bitwarden Password Manager app context app:authenticator Bitwarden Authenticator app context labels Jul 10, 2026
@david-livefront
david-livefront force-pushed the PM-40154-keystore-encrypted-shared-prefs branch from d7c24bb to aa9e67c Compare July 10, 2026 20:48
@codecov

codecov Bot commented Jul 10, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 96.36364% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 85.68%. Comparing base (274594c) to head (5427582).
⚠️ Report is 4 commits behind head on main.

Files with missing lines Patch % Lines
...en/data/datasource/disk/BaseEncryptedDiskSource.kt 94.11% 0 Missing and 1 partial ⚠️
...anager/flightrecorder/FlightRecorderManagerImpl.kt 66.66% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #7158      +/-   ##
==========================================
- Coverage   86.11%   85.68%   -0.43%     
==========================================
  Files         894     1052     +158     
  Lines       65201    68046    +2845     
  Branches     9756     9877     +121     
==========================================
+ Hits        56147    58307    +2160     
- Misses       5559     6210     +651     
- Partials     3495     3529      +34     
Flag Coverage Δ
app-data 17.98% <76.36%> (+0.17%) ⬆️
app-ui-auth-tools 18.94% <0.00%> (+0.28%) ⬆️
app-ui-platform 16.99% <0.00%> (+0.52%) ⬆️
app-ui-vault 27.92% <0.00%> (+0.52%) ⬆️
authenticator 6.12% <45.45%> (+0.05%) ⬆️
lib-core-network-bridge 4.12% <0.00%> (+<0.01%) ⬆️
lib-data-ui 1.20% <3.63%> (-0.01%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@david-livefront
david-livefront force-pushed the PM-40154-keystore-encrypted-shared-prefs branch 6 times, most recently from 18d2899 to 0405c99 Compare July 15, 2026 23:55
@david-livefront david-livefront changed the title PM-40154: Create a new Shared Preferences that encrypts data via the AndroidKeystore PM-40154: feat: Create a new Shared Preferences that encrypts data via the AndroidKeystore Jul 15, 2026
@david-livefront david-livefront added the t:feature-tool Change Type - Internal tool feature or enhancement label Jul 16, 2026
@david-livefront
david-livefront force-pushed the PM-40154-keystore-encrypted-shared-prefs branch from 0405c99 to ffcb25d Compare July 16, 2026 16:50
@github-actions github-actions Bot added t:feature Change Type - Feature Development and removed t:feature-tool Change Type - Internal tool feature or enhancement labels Jul 16, 2026
@david-livefront
david-livefront force-pushed the PM-40154-keystore-encrypted-shared-prefs branch from ffcb25d to bafe9cf Compare July 16, 2026 17:38

@quexten quexten left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we had a tech breakdown beforehand, I'd recommend implementing this in the SDK based on our modern encryption primitives instead. Given the stage, I'll comment the minimum to make this reasonably secure. I recommend dropping the separate MAC key, and switching from CBC to GCM. Currently, the API is very easy to misuse, and you generally do not want to expose separate authentication and encryption (without authentication) APIs.

@david-livefront
david-livefront force-pushed the PM-40154-keystore-encrypted-shared-prefs branch 3 times, most recently from e7abca5 to 4eb424e Compare July 17, 2026 18:45
@david-livefront
david-livefront force-pushed the PM-40154-keystore-encrypted-shared-prefs branch 3 times, most recently from 1249224 to 2fc6a32 Compare July 23, 2026 18:26
@david-livefront
david-livefront force-pushed the PM-40154-keystore-encrypted-shared-prefs branch 2 times, most recently from ff16d4c to 2be57f3 Compare July 27, 2026 14:56
@david-livefront david-livefront changed the title PM-40154: feat: Create a new Shared Preferences that encrypts data via the AndroidKeystore PM-40154: feat: Migrate user data to KeystoreEncryptedSharedPreferences Jul 27, 2026
@david-livefront
david-livefront force-pushed the PM-40154-keystore-encrypted-shared-prefs branch from 2be57f3 to 5ae0b10 Compare July 27, 2026 14:58
@quexten
quexten dismissed their stale review July 28, 2026 15:57

Outdated

@david-livefront
david-livefront force-pushed the PM-40154-keystore-encrypted-shared-prefs branch 4 times, most recently from 0592359 to db1e9a9 Compare August 4, 2026 19:26
@david-livefront
david-livefront force-pushed the PM-40154-keystore-encrypted-shared-prefs branch from db1e9a9 to 5fbdd28 Compare August 5, 2026 15:04
@david-livefront
david-livefront force-pushed the PM-40154-keystore-encrypted-shared-prefs branch from 5fbdd28 to 5427582 Compare August 5, 2026 21:33
@aj-rosado
aj-rosado marked this pull request as ready for review August 7, 2026 20:59
@aj-rosado
aj-rosado requested a review from a team as a code owner August 7, 2026 20:59
@aj-rosado
aj-rosado requested review from quexten and vvolkgang August 7, 2026 20:59
@github-actions

github-actions Bot commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

🤖 Bitwarden Claude Code Review

Overall Assessment: REQUEST CHANGES

Reviewed the migration of encrypted SharedPreferences data from the deprecated EncryptedSharedPreferences to the new KeystoreEncryptedSharedPreferences, covering BaseEncryptedDiskSource.migrateKeyByPrefix, the three disk sources that consume it (app AuthDiskSourceImpl, authenticator AuthDiskSourceImpl, CookieDiskSourceImpl), the new DiskSourceMigrationLogger, and the FlightRecorderManager.isLoggingReadyFlow addition. The migration key lists for the authenticator auth disk source and the cookie disk source cover every prefix those classes read, and the removal of the bwSecureStorage: prefix is consistently reflected in the updated tests. One gap was found in the app's AuthDiskSourceImpl, where an encrypted key read during init is not covered by the new migration and is read before the migration runs.

Code Review Details
  • ❌ : profileAccountKeys is never migrated and migrateAccountKeys() runs before migrateToKeystoreEncryption(), silently downgrading V2 account cryptographic state for users upgrading from ≤ v2026.5.x
    • app/src/main/kotlin/com/x8bit/bitwarden/data/auth/datasource/disk/AuthDiskSourceImpl.kt:139

Comment on lines +139 to +140
// Migrate to the Keystore Encrypted SharedPreferences.
migrateToKeystoreEncryption()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CRITICAL: profileAccountKeys is never migrated, so migrateAccountKeys() silently reads null for users upgrading from ≤ v2026.5.x.

Details and fix

Two problems combine here:

  1. migrateToKeystoreEncryption() runs last in init, but migrateAccountKeys() (line 137) already calls getEncryptedString(key = accountKeysKey). getEncryptedString now reads only from keystoreEncryptedPreferences, which is still empty at that point.
  2. PROFILE_ACCOUNT_KEYS_KEY is not in the migrateToKeystoreEncryption() prefix list at all, so bwSecureStorage:profileAccountKeys_<userId> is never moved.

Before #7030 (shipped in v2026.6.0), account keys were written via putEncryptedString(PROFILE_ACCOUNT_KEYS_KEY…), so any user who last ran v2026.5.x or earlier still has that entry in the legacy store. For those users accountKeys resolves to null, and migrateAccountKeys falls through to getString(privateKeyKey). AccountKeysJson?.toAccountCryptographicState then returns WrappedAccountCryptographicState.V1 because signingKey, securityState, and signedPublicKey are all null — a V2 account is silently downgraded to V1.

Side effect: putEncryptedString(key = accountKeysKey, value = null) now clears the new store (a no-op), so the legacy bwSecureStorage:profileAccountKeys_* entry is left behind in the deprecated EncryptedSharedPreferences indefinitely. That also keeps encryptedPreferences.all.size > 2, suppressing the new DiskSourceMigrationLogger signal.

Suggested fix — run the keystore migration right after the legacy secure storage migration (which populates encryptedSharedPreferences) but before the other migrations, and include the profile account keys prefix:

init {
    legacySecureStorageMigrator.migrateIfNecessary()

    // Migrate to the Keystore Encrypted SharedPreferences before any encrypted values are read.
    migrateToKeystoreEncryption()

    migrateAccountTokens()
    removeLegacyUserKeys()
    migrateAccountKeys()
}

and in migrateToKeystoreEncryption():

isMigrated = migrateKeyByPrefix(keyPrefix = PROFILE_ACCOUNT_KEYS_KEY) || isMigrated

A regression test seeding bwSecureStorage:profileAccountKeys_<userId> and asserting a V2 WrappedAccountCryptographicState survives the upgrade would lock this down.

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

Labels

app:authenticator Bitwarden Authenticator app context app:password-manager Bitwarden Password Manager app context t:feature Change Type - Feature Development

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants