Skip to content

Commit 2a045db

Browse files
committed
Restore SQLCipher support
1 parent 231a3e9 commit 2a045db

8 files changed

Lines changed: 77 additions & 17 deletions

File tree

.github/workflows/check.yml

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,14 @@ jobs:
8383
working-directory: packages/powersync
8484
run: dart test -p chrome,vm
8585

86-
- name: Enable encryption
87-
run: dart tool/enable_encryption.dart
86+
- name: Enable SQLite3 Multiple Ciphers
87+
run: dart tool/enable_encryption.dart sqlite3mc
88+
- name: Encryption tests
89+
working-directory: packages/powersync
90+
run: dart test -p vm -P encryption
91+
92+
- name: Enable SQLCipher
93+
run: dart tool/enable_encryption.dart sqlcipher
8894

8995
- name: Encryption tests
9096
working-directory: packages/powersync

packages/powersync/CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
## 2.3.1 (unreleased)
2+
3+
- Restore SQLCipher support on native platforms.
4+
15
## 2.3.0
26

37
- Add `SyncOptions.httpClient` to `SyncOptions`. It can be set to make PowerSync use a custom HTTP client when connecting to the PowerSync Service.

packages/powersync/lib/src/database/encryption_options.dart

Lines changed: 43 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,15 @@ const _isWeb = _isCompilingToJavaScript || _isDart2Wasm;
1616
///
1717
/// On native platforms, the `sqlite3` package provides a copy of SQLite with
1818
/// your app. To use encryption, we need to replace SQLite with
19-
/// [SQLite3MultipleCiphers](https://utelle.github.io/SQLite3MultipleCiphers/).
19+
/// [SQLite3MultipleCiphers](https://utelle.github.io/SQLite3MultipleCiphers/)
20+
/// or [SQLCipher](https://www.zetetic.net/sqlcipher/).
2021
/// To enable that, add this to your `pubspec.yaml`:
2122
///
2223
/// ```yaml
2324
/// hooks:
2425
/// user_defines:
2526
/// sqlite3:
26-
/// source: sqlite3mc
27+
/// source: sqlite3mc # or sqlcipher
2728
/// ```
2829
///
2930
/// If you're using pub workspaces, this needs to be added to the `pubspec.yaml`
@@ -38,6 +39,8 @@ const _isWeb = _isCompilingToJavaScript || _isDart2Wasm;
3839
/// To use encryption, download `sqlite3mc.wasm` as `web/sqlite3.wasm`. If you
3940
/// use the `powersync:setup_web` tool to download that file, pass the
4041
/// `--encryption` option.
42+
///
43+
/// Note that SQLCipher is not available on the web.
4144
final class EncryptionOptions {
4245
/// The key used to encrypt the database file.
4346
///
@@ -59,8 +62,12 @@ final class EncryptionOptions {
5962
this.sqlcipherCompatibility = !_isWeb,
6063
});
6164

62-
Iterable<String>? pragmaStatements() sync* {
63-
if (sqlcipherCompatibility) {
65+
Iterable<String> pragmaStatements({
66+
EncryptedSqliteVariant variant =
67+
EncryptedSqliteVariant.sqlite3MultipleCiphers,
68+
}) sync* {
69+
if (sqlcipherCompatibility &&
70+
variant == EncryptedSqliteVariant.sqlite3MultipleCiphers) {
6471
yield "PRAGMA cipher = 'sqlcipher'";
6572
yield 'PRAGMA legacy = 4';
6673
}
@@ -71,6 +78,7 @@ final class EncryptionOptions {
7178

7279
/// Throws if the `cipher` pragma doesn't exist, as that indicates that
7380
/// SQLite3MultipleCiphers is not available.
81+
@Deprecated('Unused in PowerSync SDK')
7482
static void checkHasCipherPragma(CommonDatabase database) {
7583
if (database.select('pragma cipher').isEmpty) {
7684
throw UnsupportedError(
@@ -80,3 +88,34 @@ final class EncryptionOptions {
8088
}
8189
}
8290
}
91+
92+
/// A fork of SQLite with encryption support.
93+
enum EncryptedSqliteVariant {
94+
/// [SQLCipher](https://www.zetetic.net/sqlcipher/) can encrypt databases with
95+
/// AES.
96+
///
97+
/// Encrypting databases with SQLCipher can be more performant than
98+
/// [sqlite3MultipleCiphers] because it uses optimized system encryption
99+
/// libraries (on Apple platform) and OpenSSL (on other platforms).
100+
///
101+
/// Note that SQLCipher is not available on the web.
102+
sqlcipher,
103+
104+
/// [SQLite3 Multiple Ciphers](https://utelle.github.io/SQLite3MultipleCiphers/)
105+
/// provides compatibility with multiple encryption schemes from a single
106+
/// build.
107+
///
108+
/// On the web, this is the only option available to encrypt databases.
109+
sqlite3MultipleCiphers;
110+
111+
/// The [EncryptedSqliteVariant] enabled on the database, or null.
112+
static EncryptedSqliteVariant? resolveOnDatabase(CommonDatabase db) {
113+
if (db.select('pragma cipher').isNotEmpty) {
114+
return EncryptedSqliteVariant.sqlite3MultipleCiphers;
115+
} else if (db.select('pragma cipher_version').isNotEmpty) {
116+
return EncryptedSqliteVariant.sqlcipher;
117+
} else {
118+
return null;
119+
}
120+
}
121+
}

packages/powersync/lib/src/open_factory/native/native_open_factory.dart

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ base class NativePowerSyncOpenFactory extends NativeSqliteOpenFactory {
2222
@override
2323
List<String> pragmaStatements(SqliteOpenOptions options) {
2424
return [
25-
...?encryptionOptions?.pragmaStatements(),
2625
...super.pragmaStatements(options),
2726
'PRAGMA recursive_triggers = TRUE',
2827
];
@@ -58,7 +57,7 @@ base class NativePowerSyncOpenFactory extends NativeSqliteOpenFactory {
5857
var retryDelay = 2;
5958
while (stopwatch.elapsedMilliseconds < 500) {
6059
try {
61-
return super.openNativeConnection(options);
60+
return openConnectionAttempt(options);
6261
} catch (e) {
6362
if (e is SqliteException && e.resultCode == 5) {
6463
sleep(Duration(milliseconds: retryDelay));
@@ -77,8 +76,18 @@ base class NativePowerSyncOpenFactory extends NativeSqliteOpenFactory {
7776

7877
@override
7978
void configureConnection(Database database, SqliteOpenOptions options) {
80-
if (encryptionOptions != null) {
81-
EncryptionOptions.checkHasCipherPragma(database);
79+
if (encryptionOptions case final encryption?) {
80+
final resolved = EncryptedSqliteVariant.resolveOnDatabase(database);
81+
if (resolved == null) {
82+
throw UnsupportedError(
83+
'Tried to use encryption, but SQLite3MultipleCiphers is not available. '
84+
'Consult the documentation on EncryptionOptions on how to resolve this.',
85+
);
86+
}
87+
88+
for (final pragma in encryption.pragmaStatements(variant: resolved)) {
89+
database.execute(pragma);
90+
}
8291
}
8392

8493
super.configureConnection(database, options);
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
const String libraryVersion = '2.3.0';
1+
const String libraryVersion = '2.3.1';

packages/powersync/pubspec.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
name: powersync
2-
version: 2.3.0
2+
version: 2.3.1
33
homepage: https://powersync.com
44
repository: https://github.com/powersync-ja/powersync.dart
55
description: PowerSync Dart and Flutter SDK. Sync Postgres, MongoDB, MySQL or SQL Server with SQLite in your app
@@ -10,7 +10,7 @@ environment:
1010
dependencies:
1111
sqlite_async: ^0.14.3
1212
sqlite3_web: ^0.9.0
13-
sqlite3: ^3.2.0
13+
sqlite3: ^3.3.3
1414
meta: ^1.0.0
1515
http: ^1.6.0
1616
uuid: ^4.2.0

pubspec.lock

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1497,10 +1497,10 @@ packages:
14971497
dependency: transitive
14981498
description:
14991499
name: sqlite3
1500-
sha256: "9488c7d2cdb1091c91cacf7e207cff81b28bff8e366f042bad3afe7d34afe189"
1500+
sha256: "37356bcb56ce0d9404d602c41e4bdb7765e7e9732a3e47adb3d98c556a6abdad"
15011501
url: "https://pub.dev"
15021502
source: hosted
1503-
version: "3.3.2"
1503+
version: "3.3.3"
15041504
sqlite3_connection_pool:
15051505
dependency: transitive
15061506
description:

tool/enable_encryption.dart

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@ import 'dart:io';
44
/// encryption tests.
55
///
66
/// Must be run from the root of the repository.
7-
void main() {
7+
void main(List<String> args) {
8+
final encryption = args.isEmpty ? 'sqlite3mc' : 'sqlcipher';
9+
810
final file = File('pubspec.yaml');
911
final updated = file
1012
.readAsStringSync()
11-
.replaceFirst('source: sqlite3', 'source: sqlite3mc');
13+
.replaceFirst('source: sqlite3', 'source: $encryption');
1214
file.writeAsStringSync(updated);
1315
}

0 commit comments

Comments
 (0)