Skip to content

Changes - #1

Merged
Attia2003 merged 2 commits into
masterfrom
changes
Apr 8, 2026
Merged

Changes#1
Attia2003 merged 2 commits into
masterfrom
changes

Conversation

@Attia2003

@Attia2003 Attia2003 commented Apr 5, 2026

Copy link
Copy Markdown
Owner

Summary by CodeRabbit

  • Improvements
    • Enhanced database security with encrypted data storage to protect sensitive information at rest. Updated database initialization to incorporate encryption functionality, ensuring user data remains secure.

@coderabbitai

coderabbitai Bot commented Apr 5, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

The changes introduce SQLCipher-based database encryption. A new DatabasePassphrase class manages encrypted storage and retrieval of encryption keys using AndroidX Security, and the dependency injection layer is updated to provide and integrate this passphrase with Room's database builder via SQLCipher's SupportFactory.

Changes

Cohort / File(s) Summary
Gradle Dependencies
app/build.gradle.kts
Added android-database-sqlcipher:4.5.4 and androidx.sqlite:sqlite-ktx:2.4.0 dependencies.
Database Encryption Key Management
app/src/main/java/com/example/fakestore/core/data/local/DatabasePassphrase.kt
New class that lazily initializes AndroidX MasterKey (AES-256-GCM) and EncryptedSharedPreferences. Exposes getPassphrase() to retrieve stored encryption passphrases or generate new 32-byte random ones if absent. Includes utility methods for hex encoding/decoding.
Dependency Injection Configuration
app/src/main/java/com/example/fakestore/core/di/network/DatabaseModule.kt
Added provideDatabasePassphrase() provider. Updated provideAppDatabase() to accept DatabasePassphrase dependency and integrate SQLCipher via SupportFactory for encrypted database initialization.
Formatting
app/src/main/java/com/example/fakestore/core/data/repository/CartRepoImpl.kt
Inserted two blank lines; no functional changes.

Sequence Diagram(s)

sequenceDiagram
    actor App as Application
    participant HiltDI as Hilt DI
    participant DProv as DatabaseModule
    participant DBPass as DatabasePassphrase
    participant AndroidXSec as AndroidX Security
    participant ESP as EncryptedSharedPrefs
    participant Room as Room Database
    participant SQLCipher as SQLCipher

    App->>HiltDI: Request AppDatabase
    HiltDI->>DProv: Call provideAppDatabase()
    
    DProv->>DProv: Request DatabasePassphrase dependency
    HiltDI->>DProv: Call provideDatabasePassphrase()
    DProv->>DBPass: new DatabasePassphrase(context)
    
    DBPass->>AndroidXSec: Initialize MasterKey (AES-256-GCM)
    AndroidXSec-->>DBPass: MasterKey ready
    
    DBPass->>ESP: Create EncryptedSharedPreferences
    ESP-->>DBPass: Preferences instance ready
    
    DBPass-->>DProv: DatabasePassphrase instance
    
    DProv->>DBPass: Call getPassphrase()
    DBPass->>ESP: Query stored passphrase
    
    alt Passphrase exists
        ESP-->>DBPass: Return hex-encoded passphrase
        DBPass->>DBPass: Decode hex to ByteArray
    else Passphrase missing
        DBPass->>DBPass: Generate 32-byte random passphrase
        DBPass->>ESP: Store as hex string
        ESP-->>DBPass: Stored
    end
    
    DBPass-->>DProv: ByteArray passphrase
    
    DProv->>SQLCipher: Create SupportFactory(passphrase)
    SQLCipher-->>DProv: Factory ready
    
    DProv->>Room: Build database with openHelperFactory()
    Room->>SQLCipher: Initialize encrypted database
    SQLCipher-->>Room: Database opened
    Room-->>DProv: AppDatabase instance
    DProv-->>HiltDI: AppDatabase instance
    HiltDI-->>App: AppDatabase ready
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 Encryption keys now safely nest,
Behind AndroidX Security's best,
SQLCipher guards our data tight,
With AES-256 holding fast through the night! ✨

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'Changes' is too vague and generic, providing no meaningful information about the specific changeset. Use a descriptive title that reflects the main change, such as 'Add SQLCipher encryption for database security' or 'Integrate SQLCipher and encrypted preferences'
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch changes

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@sonarqubecloud

sonarqubecloud Bot commented Apr 5, 2026

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3aff2029f6

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines 37 to 40
"fakestore_db"
)
.openHelperFactory(factory)
.fallbackToDestructiveMigration()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Handle plaintext-to-SQLCipher upgrade before opening DB

This change opens the existing fakestore_db file through SQLCipher without any transition path from the previous plaintext Room database, so upgraded installs with an existing DB will fail on startup when SQLCipher tries to read the old file with a key (Room's fallbackToDestructiveMigration() does not handle this case). Add an explicit one-time plaintext→encrypted migration or a controlled wipe path before enabling openHelperFactory(factory) on the same file.

Useful? React with 👍 / 👎.

Comment on lines +43 to +45
prefs.edit()
.putString(KEY_PASSPHRASE, bytesToHex(passphrase))
.apply()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Persist generated passphrase synchronously

The first-time passphrase write uses apply(), which is asynchronous; if the process is killed after returning the new key but before the preference is flushed, the next launch will generate a different passphrase and the already-created encrypted DB can no longer be opened. This can cause startup failures or forced data loss on cold-start edge cases, so the initial key write should be committed synchronously.

Useful? React with 👍 / 👎.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (1)
app/build.gradle.kts (1)

59-60: Use the supported SQLCipher artifact.

net.zetetic:android-database-sqlcipher is officially deprecated and no longer updated, and SQLCipher points new Android work to sqlcipher-android as the long-term replacement. Starting this rollout on the deprecated package will make future compatibility work harder than it needs to be. (github.com)

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/build.gradle.kts` around lines 59 - 60, The build is using the deprecated
artifact implementation("net.zetetic:android-database-sqlcipher:4.5.4"); replace
it with the supported SQLCipher artifact
implementation("net.zetetic:sqlcipher-android:4.5.4") (leave
implementation("androidx.sqlite:sqlite-ktx:2.4.0") intact), and verify any
import/package usages or ProGuard/R8 rules that referenced the old artifact are
updated to match the new sqlcipher-android coordinates.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@app/src/main/java/com/example/fakestore/core/data/local/DatabasePassphrase.kt`:
- Around line 43-45: The prefs write currently uses apply() which is
asynchronous; change it to use commit() so the generated passphrase is persisted
synchronously: replace prefs.edit().putString(KEY_PASSPHRASE,
bytesToHex(passphrase)).apply() with a call to commit() and check its boolean
result, and if commit() returns false fail fast (throw an exception or
crash/abort startup) so the app does not continue with an unwritten passphrase
that would make the encrypted DB unreadable; update the code in
DatabasePassphrase.kt around the prefs.edit() call that writes KEY_PASSPHRASE
and bytesToHex(passphrase).
- Around line 23-30: The EncryptedSharedPreferences instance created in
DatabasePassphrase (prefs / PREFS_FILE = "fakestore_db_passphrase") must be
explicitly excluded from backups; update backup_rules.xml to add an <exclude
domain="sharedpref" path="fakestore_db_passphrase.xml"/> alongside any includes,
and update data_extraction_rules.xml to add a corresponding <exclude
domain="sharedpref" path="fakestore_db_passphrase.xml"/> inside <cloud-backup>;
ensure the excluded filename matches the PREFS_FILE used by the prefs
initialization so the encrypted passphrase file is not backed up.

In `@app/src/main/java/com/example/fakestore/core/di/network/DatabaseModule.kt`:
- Around line 28-40: The current provideAppDatabase uses
SupportFactory(passphrase.getPassphrase()) against the hardcoded name
"fakestore_db", which will crash if an existing plaintext DB file is present;
before calling Room.databaseBuilder you must detect and handle an existing
unencrypted DB file (using the same name) and either encrypt it or remove/rotate
it. Update provideAppDatabase to: check for the presence of the "fakestore_db"
file, determine if it is unencrypted (use SQLCipherUtils or a small header
check), then perform an in-place encrypt/migrate to SQLCipher (via
SQLCipherUtils) or delete/rename the old file to a retired name (or switch to a
new DB name) before constructing the Room database with
SupportFactory(passphrase.getPassphrase()); ensure this detection/migration runs
synchronously before Room.databaseBuilder is invoked.

---

Nitpick comments:
In `@app/build.gradle.kts`:
- Around line 59-60: The build is using the deprecated artifact
implementation("net.zetetic:android-database-sqlcipher:4.5.4"); replace it with
the supported SQLCipher artifact
implementation("net.zetetic:sqlcipher-android:4.5.4") (leave
implementation("androidx.sqlite:sqlite-ktx:2.4.0") intact), and verify any
import/package usages or ProGuard/R8 rules that referenced the old artifact are
updated to match the new sqlcipher-android coordinates.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 3959347c-a8cb-4085-b5e9-7570721dc842

📥 Commits

Reviewing files that changed from the base of the PR and between 27bfc25 and 3aff202.

📒 Files selected for processing (4)
  • app/build.gradle.kts
  • app/src/main/java/com/example/fakestore/core/data/local/DatabasePassphrase.kt
  • app/src/main/java/com/example/fakestore/core/data/repository/CartRepoImpl.kt
  • app/src/main/java/com/example/fakestore/core/di/network/DatabaseModule.kt

Comment on lines +23 to +30
private val prefs by lazy {
EncryptedSharedPreferences.create(
context,
PREFS_FILE,
masterKey,
EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

manifest=$(fd -p AndroidManifest.xml app/src/main | head -n1)
if [[ -n "${manifest:-}" ]]; then
  rg -n -C2 'allowBackup|fullBackupContent|dataExtractionRules' "$manifest"
fi

fd -e xml . app/src/main/res/xml | while read -r file; do
  rg -n -C2 'fakestore_db_passphrase|sharedpref|shared_prefs|exclude|include' "$file" || true
done

Repository: Attia2003/FakeStore

Length of output: 884


Add explicit exclusion for the passphrase store in backup rules.

The fakestore_db_passphrase shared preferences file is not excluded from backup. Currently, backup_rules.xml has the include rules commented out and data_extraction_rules.xml contains only TODO placeholders with no active configuration. This means the encrypted passphrase store will be backed up along with its parent app data. Since EncryptedSharedPreferences relies on platform keystore material to decrypt the file—which typically doesn't exist after app restoration—the passphrase will be unrecoverable after backup restore, making the database inaccessible.

Add an explicit <exclude> rule for fakestore_db_passphrase in both backup_rules.xml and data_extraction_rules.xml:

Example backup_rules.xml
<full-backup-content>
    <include domain="sharedpref" path="."/>
    <exclude domain="sharedpref" path="fakestore_db_passphrase.xml"/>
</full-backup-content>
Example data_extraction_rules.xml
<data-extraction-rules>
    <cloud-backup>
        <exclude domain="sharedpref" path="fakestore_db_passphrase.xml"/>
    </cloud-backup>
</data-extraction-rules>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@app/src/main/java/com/example/fakestore/core/data/local/DatabasePassphrase.kt`
around lines 23 - 30, The EncryptedSharedPreferences instance created in
DatabasePassphrase (prefs / PREFS_FILE = "fakestore_db_passphrase") must be
explicitly excluded from backups; update backup_rules.xml to add an <exclude
domain="sharedpref" path="fakestore_db_passphrase.xml"/> alongside any includes,
and update data_extraction_rules.xml to add a corresponding <exclude
domain="sharedpref" path="fakestore_db_passphrase.xml"/> inside <cloud-backup>;
ensure the excluded filename matches the PREFS_FILE used by the prefs
initialization so the encrypted passphrase file is not backed up.

Comment on lines +43 to +45
prefs.edit()
.putString(KEY_PASSPHRASE, bytesToHex(passphrase))
.apply()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Persist the generated passphrase synchronously.

apply() is async. If the process dies before this write reaches disk, the next launch can generate a different passphrase and the existing encrypted DB becomes unreadable. Use commit() here and fail fast if it returns false.

🐛 Proposed fix
-        prefs.edit()
-            .putString(KEY_PASSPHRASE, bytesToHex(passphrase))
-            .apply()
+        val persisted = prefs.edit()
+            .putString(KEY_PASSPHRASE, bytesToHex(passphrase))
+            .commit()
+        check(persisted) { "Failed to persist database passphrase" }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
prefs.edit()
.putString(KEY_PASSPHRASE, bytesToHex(passphrase))
.apply()
val persisted = prefs.edit()
.putString(KEY_PASSPHRASE, bytesToHex(passphrase))
.commit()
check(persisted) { "Failed to persist database passphrase" }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@app/src/main/java/com/example/fakestore/core/data/local/DatabasePassphrase.kt`
around lines 43 - 45, The prefs write currently uses apply() which is
asynchronous; change it to use commit() so the generated passphrase is persisted
synchronously: replace prefs.edit().putString(KEY_PASSPHRASE,
bytesToHex(passphrase)).apply() with a call to commit() and check its boolean
result, and if commit() returns false fail fast (throw an exception or
crash/abort startup) so the app does not continue with an unwritten passphrase
that would make the encrypted DB unreadable; update the code in
DatabasePassphrase.kt around the prefs.edit() call that writes KEY_PASSPHRASE
and bytesToHex(passphrase).

Comment on lines +28 to 40
fun provideAppDatabase(
@ApplicationContext context: Context,
passphrase: DatabasePassphrase
): AppDatabase {
val factory = SupportFactory(passphrase.getPassphrase())

return Room.databaseBuilder(
context,
AppDatabase::class.java,
"fakestore_db"
)
.openHelperFactory(factory)
.fallbackToDestructiveMigration()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

find . -type f -name "AppDatabase.kt" | head -20

Repository: Attia2003/FakeStore

Length of output: 138


🏁 Script executed:

fd -t f "AppDatabase.kt"

Repository: Attia2003/FakeStore

Length of output: 136


🏁 Script executed:

rg -t kotlin "@Database" -A 5 --max-count 10

Repository: Attia2003/FakeStore

Length of output: 647


🏁 Script executed:

fd -t f -name "*Migration*" | head -20

Repository: Attia2003/FakeStore

Length of output: 293


🏁 Script executed:

rg "Migration" -t kotlin -C 3

Repository: Attia2003/FakeStore

Length of output: 731


🏁 Script executed:

rg "SupportFactory" -t kotlin -C 5

Repository: Attia2003/FakeStore

Length of output: 2355


🌐 Web query:

Room database fallbackToDestructiveMigration SQLCipher plaintext encrypted migration

💡 Result:

Room database's fallbackToDestructiveMigration does not reliably handle migration from a plaintext SQLite database to an encrypted SQLCipher database. When switching to SQLCipher by adding .openHelperFactory(SupportFactory(passphrase)), attempting to open an existing unencrypted database with SQLCipher fails with "file is not a database" or similar SQLiteException, as SQLCipher expects an encrypted file or correct key but the plaintext file lacks SQLCipher headers. Room throws an exception before reaching fallback logic, preventing destructive recreation while preserving data. To migrate while preserving data from plaintext to encrypted: 1. Use SQLCipherUtils (from cwac-saferoom or implement similar) to check state with SQLCipherUtils.getDatabaseState(context, dbFile). 2. If UNENCRYPTED, manually encrypt using SQLCipherUtils.encrypt(context, dbName, passphrase) or encryptTo with temp file for safety (backup original, encrypt copy, swap/rename files, delete backup). 3. Then build Room database with .openHelperFactory(SupportFactory(passphrase)). Example code (Kotlin, from authoritative sources): val dbFile = context.getDatabasePath(DB_NAME) val passphrase = PASSPHRASE.toByteArray val state = SQLCipherUtils.getDatabaseState(context, dbFile) if (state == SQLCipherUtils.State.UNENCRYPTED) { val dbTemp = context.getDatabasePath("_temp.db") dbTemp.delete SQLCipherUtils.encryptTo(context, dbFile, dbTemp, passphrase) // Handle rollback if needed } return Room.databaseBuilder(context, ToDoDatabase::class.java, DB_NAME) .openHelperFactory(SupportFactory(passphrase)) .build If data loss is acceptable, delete the database file manually before building (context.deleteDatabase(DB_NAME)), then fallbackToDestructiveMigration will create a new encrypted one. Normal schema migrations (e.g., adding columns) work fine with SQLCipher using standard Room migrations. SQLCipherUtils source is in https://github.com/commonsguy/cwac-saferoom. Official Room docs cover fallbackToDestructiveMigration for schema changes only, not engine switches. No official Android docs for SQLCipher integration; use Zetetic's SQLCipher for Android library.

Citations:


🌐 Web query:

SupportFactory SQLCipher opening plaintext database Room crash behavior

💡 Result:

When using SupportFactory from SQLCipher for Android with Room to open a plaintext (unencrypted) SQLite database, the app crashes. SQLCipher expects an encrypted database matching the provided passphrase. Attempting to open a plaintext database with a non-empty passphrase passphrase fails, typically with errors like "file is not a database" (SQLite error code 26), "file is encrypted or is not a database", or "database disk image is malformed". This occurs because SQLCipher cannot decrypt the plaintext file as if it were encrypted, leading to corruption detection during the keying process in SQLiteDatabase.openDatabaseInternal or keyDatabase. To handle this scenario safely (e.g., during migration from plaintext to encrypted), first detect if the database is plaintext using SQLCipherUtils.getDatabaseState from CommonsWare examples or by attempting to open with empty passphrase (""). If unencrypted, encrypt it using sqlcipher_export via ATTACH DATABASE with appropriate KEY clauses before using SupportFactory with your passphrase. Direct opening with SupportFactory on plaintext will always fail and crash the app. Sources confirm this behavior across GitHub issues (#527, #579, #629, #22), StackOverflow migration posts, and CommonsWare SQLCipher chapters (,,). Official SQLCipher docs (,) describe SupportFactory for encrypted DBs only; no support for mixed plaintext/encrypted without manual migration.

Citations:


🏁 Script executed:

rg "deleteDatabase|getDatabaseState|encrypt" -t kotlin --max-count 20

Repository: Attia2003/FakeStore

Length of output: 121


🏁 Script executed:

rg "provideAppDatabase|DatabaseModule" -B 5 -A 15 -t kotlin

Repository: Attia2003/FakeStore

Length of output: 3144


Existing plaintext fakestore_db will cause app crash on startup.

When SupportFactory attempts to open an existing unencrypted database with a passphrase, it fails with "file is not a database" or similar error during the encryption key application—this happens before fallbackToDestructiveMigration() can trigger (which only applies to schema version changes). Apps will crash on any device that has the old plaintext fakestore_db. Either implement explicit encryption using SQLCipherUtils (check state, encrypt in-place or to temp file, then swap files), manually delete the old database before first encrypted access, or rotate to a new database name and retire the old file.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/src/main/java/com/example/fakestore/core/di/network/DatabaseModule.kt`
around lines 28 - 40, The current provideAppDatabase uses
SupportFactory(passphrase.getPassphrase()) against the hardcoded name
"fakestore_db", which will crash if an existing plaintext DB file is present;
before calling Room.databaseBuilder you must detect and handle an existing
unencrypted DB file (using the same name) and either encrypt it or remove/rotate
it. Update provideAppDatabase to: check for the presence of the "fakestore_db"
file, determine if it is unencrypted (use SQLCipherUtils or a small header
check), then perform an in-place encrypt/migrate to SQLCipher (via
SQLCipherUtils) or delete/rename the old file to a retired name (or switch to a
new DB name) before constructing the Room database with
SupportFactory(passphrase.getPassphrase()); ensure this detection/migration runs
synchronously before Room.databaseBuilder is invoked.

@Attia2003
Attia2003 merged commit 4afd603 into master Apr 8, 2026
2 checks passed
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.

1 participant