sqflite database opened without SQLCipher / page-level encryption
ID |
dart.storage.sqflite_no_sqlcipher |
Severity |
low |
Remediation Complexity |
medium |
Remediation Risk |
medium |
Remediation Effort |
medium |
Resource |
Storage |
Language |
Dart |
Description
A sqflite database is opened without SQLCipher / page-level encryption. The .db file is plaintext on device and trivially exfiltrated via backup, root, or USB debugging.
Rationale
package:sqflite wraps the bundled SQLite binary, which stores
data in a plain file on disk. Like Hive and SharedPreferences,
the OS sandbox protects it from other apps but does NOT encrypt
it. The exposure paths are the same: rooted devices, backups,
forensic imaging, USB debugging on developer-mode devices.
For Flutter apps holding any authentication or user-identifiable
data, this is rarely acceptable in production. The drop-in
alternative is sqflite_sqlcipher, which uses SQLCipher (an
AES-256-CBC page-encrypted fork of SQLite) and accepts a
password: argument on openDatabase. The encryption key must
itself be stored in flutter_secure_storage.
Confidence is LOW because the rule is a per-file heuristic: it
fires on openDatabase(…) in any file that doesn’t also
contain a password: argument. A central DB-factory file that
passes the password may legitimately not appear in callers — in
that case the finding is a triage-time false positive.
The following code illustrates a vulnerable pattern detected by this rule:
class PlainSqlite {
Future<Database> open() async {
final path = join(await getDatabasesPath(), 'app.db');
// VULNERABLE: sqflite database opened without SQLCipher / page-level encryption
final db = await openDatabase(path, version: 1, onCreate: (db, v) async {
await db.execute('CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT)');
});
return db;
}
}
Remediation
Switch the import to sqflite_sqlcipher and pass a password:
argument derived from a key stored in flutter_secure_storage.
// Vulnerable
import 'package:sqflite/sqflite.dart';
final db = await openDatabase('app.db', version: 1, onCreate: ...);
// Fixed
import 'package:sqflite_sqlcipher/sqflite.dart';
const storage = FlutterSecureStorage();
final password = await storage.read(key: 'db_key') ?? _provisionKey(storage);
final db = await openDatabase(
'app.db',
version: 1,
password: password,
onCreate: ...
);
References
-
OWASP Top 10 2025 - A04 : Cryptographic Failures.
-
SQLCipher — AES-256 page-encrypted fork of SQLite that powers
sqflite_sqlcipher -
package:sqflite_sqlcipher (pub.dev) — Drop-in replacement that adds a
password:named argument toopenDatabase -
Talsec — OWASP Mobile Top 10 for Flutter M9 — Insecure data storage in Flutter / Dart, with SQLite-specific guidance