Add encrypted SQLite offline store support via SQLite3 Multiple Ciphers#1
Merged
Conversation
SQLitePCLRaw 3.0 (pulled in by EF Core 10) removed the free encryption bundles (bundle_e_sqlcipher / bundle_e_sqlite3mc). The maintainer's recommended replacement - the SQLite Encryption Extension (SEE) - requires a paid license. This adds a free, no-paid-license way to encrypt the offline client store using the open-source, MIT-licensed SQLite3 Multiple Ciphers engine. - New package CommunityToolkit.Datasync.Client.EncryptedSqlite providing UseEncryptedSqlite() EF Core extensions, EncryptedSqliteOptions (SQLCipher format compatibility), EncryptedSqliteFactory, and PRAGMA rekey support. - Switch the base Client to Microsoft.EntityFrameworkCore.Sqlite.Core so the consumer chooses a single SQLitePCLRaw bundle (plain bundle_e_sqlite3 or the new encrypted package). A project must reference exactly one bundle. - Self-contained test project, solution + signed-package-list wiring, and a new docs page (in-depth/client/encryption.md) plus nav/cross-links. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NXcj1dFqpm6qVWLAmc857D
Two EncryptedSqlite tests failed in CI (the build compiled cleanly):
- Rekey: the SQLite3 Multiple Ciphers build opens databases in WAL journal
mode, where PRAGMA rekey is rejected ("Rekeying is not supported in WAL
journal mode"). RekeyEncryptedSqlite now switches to a rollback journal
(PRAGMA journal_mode = DELETE) before re-keying.
- SQLCipher-compat reopen: EncryptedSqliteOptions.Apply escaped the key via
"SELECT quote(...)", which executes a query before PRAGMA key is set, so
reading an already-encrypted database failed with "file is not a database".
The key/cipher PRAGMAs are now applied as the first statements, with the
literal escaped via a new internal SqliteLiteral.Quote helper (no pre-key
query).
Also tidied warnings in the new files: enable nullable in the test project
(CS8632), implement the full IDisposable pattern in the test base (CA1063),
and justify-suppress CA2100 where escaped values are embedded in PRAGMA text.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NXcj1dFqpm6qVWLAmc857D
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.
Summary
This PR adds support for encrypted SQLite offline stores to the Datasync Toolkit client, addressing the gap created by SQLitePCLRaw 3.0's removal of free encryption bundles. The implementation uses SQLite3 Multiple Ciphers (SQLite3MC), a free, MIT-licensed encryption engine that is compatible with SQLCipher databases.
Key Changes
New Package:
CommunityToolkit.Datasync.Client.EncryptedSqliteUseEncryptedSqlite()extension methods for configuring encrypted SQLite storesEncryptedSqliteFactoryfor creating and managing encrypted connectionsEncryptedSqliteOptions(e.g., SQLCipher compatibility)RekeyEncryptedSqlite()for changing encryption keys viaPRAGMA rekeyCore Implementation:
EncryptedSqliteDbContextOptionsExtensions: Two overloads ofUseEncryptedSqlite()- one accepting a connection string with password, another accepting a pre-configuredSqliteConnectionEncryptedSqliteFactory: Helper for creating and opening encrypted connections with optional cipher configurationEncryptedSqliteOptions: Configuration class for non-default cipher schemes and legacy compatibility levelsSqliteBatteries: Thread-safe initialization of the SQLite3MC provider viaSQLitePCL.Batteries_V2.Init()Base Package Change:
CommunityToolkit.Datasync.Client.csprojto referenceMicrosoft.EntityFrameworkCore.Sqlite.Core(bundle-less) instead of the fullMicrosoft.EntityFrameworkCore.Sqlitepackage, allowing consumers to choose their SQLitePCLRaw bundleComprehensive Tests:
EncryptedSqlite_Tests: Validates encryption round-trips, file encryption verification, wrong password rejection, and key rotationEncryptedSqlite_OfflineDbContext_Tests: Verifies integration withOfflineDbContextEncryptedSqliteTestBase: Reusable test infrastructure with helpers for seeding, reading, and asserting encryptionDocumentation: New
encryption.mdguide covering setup, key management, and SQLCipher compatibilityNotable Implementation Details
quote()function rather than string concatenation to prevent injectionTestCommonto prevent SQLitePCLRaw bundle conflictsUseEncryptedSqlite(string, password)manages the connection;UseEncryptedSqlite(SqliteConnection)requires caller disposalhttps://claude.ai/code/session_01NXcj1dFqpm6qVWLAmc857D