sqflite_sqlcipher 2.2.1以上版本打开SQLCipher3加密DB时内存不足崩溃
使用sqflite_sqlcipher 2.2.1及以上版本时,打开SQLCipher3加密的数据库会崩溃,相同代码在2.2.1版本可正常运行。
代码片段
Future<Database> _open(String databasePath, String key) async { try { if (key.isEmpty) { return await openDatabase(databasePath, version: 1); } else { return await openDatabase(databasePath, password: key, version: 1); } } catch (e) { print( "Error opening database normally: $e. Retrying with cipher compatibility..."); return await openDatabase( databasePath, password: key, version: 1, onConfigure: (db) async { await db.execute('PRAGMA cipher_compatibility = 3;'); }, ); } }
错误日志
flutter: Error opening database normally: DatabaseException(open_failed ..../Documents/9781316644607.db). Retrying with cipher compatibility...
flutter: error DatabaseException(Error Domain=FMDatabase Code=7 "out of memory" UserInfo={NSLocalizedDescription=out of memory}) sql 'BEGIN EXCLUSIVE' args [] during open, closing...
[ERROR:flutter/runtime/dart_vm_initializer.cc(40)] Unhandled Exception: DatabaseException(Error Domain=FMDatabase Code=7 "out of memory" UserInfo={NSLocalizedDescription=out of memory}) sql 'BEGIN EXCLUSIVE' args []
#0 SqfliteSqlCipherDatabaseFactoryImpl.wrapDatabaseException (package:sqflite_sqlcipher/src/factory_sql_cipher_impl.dart:44:9)
#1 SqfliteDatabaseMixin.txnSynchronized (package:sqflite_common/src/database_mixin.dart:554:16)
#2 SqfliteDatabaseMixinExt.txnBeginTransaction (package:sqflite_common/src/database_mixin.dart:411:20)
#3 SqfliteDatabaseMixin.beginTransaction (package:sqflite_common/src/database_mixin.dart:863:5)
#4 SqfliteDatabaseMixinExt._txnTransaction (package:sqflite_common/src/database_mixin.dart:384:13)
#5 BasicLock.synchronized (package:synchronized/src/basic_lock.dart:38:16)
观察结果
- sqflite_sqlcipher 2.2.1版本可正常运行
- 2.2.1以上所有版本均失败
- 数据库采用SQLCipher3加密
问题出在高版本sqflite_sqlcipher中,onConfigure回调的执行时机已发生变化——在数据库尝试建立连接并执行初始化事务之后才会触发,此时SQLCipher3的兼容性参数还未设置,导致数据库解密失败并抛出内存错误。
正确的做法是在数据库打开前就设置SQLCipher3的兼容参数,不要依赖onConfigure回调,改用customOpenStatement参数直接指定兼容配置:
Future<Database> _open(String databasePath, String key) async { if (key.isEmpty) { return await openDatabase(databasePath, version: 1); } else { try { return await openDatabase(databasePath, password: key, version: 1); } catch (e) { print( "Error opening database normally: $e. Retrying with cipher compatibility..."); // 使用customOpenStatement在打开时直接设置兼容参数 return await openDatabase( databasePath, password: key, version: 1, customOpenStatement: 'PRAGMA cipher_compatibility = 3;', ); } } }
原理说明
sqflite_sqlcipher 2.2.1之后的版本升级了底层SQLCipher库,默认加密参数与SQLCipher3不兼容。而customOpenStatement会在数据库连接建立后立即执行指定SQL语句,确保解密时使用正确的兼容配置,避免了onConfigure回调时机滞后带来的问题。
内容的提问来源于stack exchange,提问作者Aakash Deep

