Database encrypted (SQLite encryption or equivalent)
Why it matters
A plaintext SQLite database file is readable by anyone with filesystem access to the device — a physical attacker, a forensic tool, or malware with elevated privileges. CWE-311 (Missing Encryption of Sensitive Data) and CWE-922 (Insecure Storage of Sensitive Information) both apply. OWASP Mobile Top 10 M9 and GDPR Art. 32 require encryption for stored personal data. Unlike AsyncStorage, a SQLite file is a structured, easily-parsed binary that can be opened in any standard tool. A single extracted database file exposes the entire local data model with no further attack required.
Severity rationale
High because an unencrypted database file can be extracted from a compromised device and parsed trivially, exposing all stored personal data with no additional attack.
Remediation
Enable database-level encryption using SQLCipher, MMKV's built-in encryption, or Realm's encryption configuration. Derive the encryption key from platform-secure storage — never hardcode it.
// MMKV with encryption (npm install react-native-mmkv)
import { MMKVLoader } from 'react-native-mmkv';
const storage = new MMKVLoader()
.setEncryption(true)
.initialize();
// Realm with encryption (npm install realm)
import Realm from 'realm';
import * as SecureStore from 'expo-secure-store';
async function openEncryptedRealm() {
const keyHex = await SecureStore.getItemAsync('realm_key');
const encryptionKey = hexToUint8Array(keyHex!);
return Realm.open({
schema: [YourSchema],
encryptionKey, // 64-byte Uint8Array
});
}
Store the database encryption key in expo-secure-store or Keychain — not in source code and not in AsyncStorage. Rotate the key on first launch if none exists.
Detection
-
ID:
database-encryption -
Severity:
high -
What to look for: If the app uses SQLite or another database, check whether database-level encryption is enabled. Look for SQLCipher integration, MMKV encryption settings, or Realm encryption configuration.
-
Pass criteria: Count all local database files or database initialization calls. At least 1 database encryption mechanism must be active (SQLCipher, MMKV encryption, or Realm encryption). No more than 0 database files should be readable without the encryption key.
-
Fail criteria: Database is stored in plain text with no encryption. Could be accessed if device is compromised.
-
Skip (N/A) when: App does not use a local database (only AsyncStorage for non-sensitive data is acceptable without encryption).
-
Detail on fail:
"SQLite database stored in plain text without encryption. Database file could be extracted and examined if device is compromised." -
Remediation: Use encrypted SQLite (SQLCipher) or encrypted alternatives:
// For Expo, use expo-sqlite with encryption: import * as SQLite from 'expo-sqlite'; export async function initEncryptedDB() { // Expo SQLite uses SQLCipher under the hood on native const db = SQLite.openDatabase('encrypted.db'); return new Promise((resolve) => { db.transaction(tx => { // For Expo, encryption is automatic on iOS/Android // For advanced encryption, consider native modules tx.executeSql('CREATE TABLE IF NOT EXISTS secure_data (id INTEGER PRIMARY KEY, data TEXT);'); resolve(db); }); }); }For react-native-mmkv (has built-in encryption):
// Install: npm install react-native-mmkv import { MMKVLoader } from 'react-native-mmkv'; const storage = new MMKVLoader() .setEncryption(true) .initialize();For Realm (has built-in encryption):
// Install: npm install realm import Realm from 'realm'; const config: Realm.Configuration = { schema: [YourSchema], encryptionKey: new Uint8Array(64), // Use a proper key from secure storage }; const realm = await Realm.open(config);
External references
- cwe · CWE-311 — Missing Encryption of Sensitive Data
- cwe · CWE-922 — Insecure Storage of Sensitive Information
- owasp:2021 · A02 — Cryptographic Failures
- gdpr · Art. 32 — Security of processing
Taxons
History
- 2026-04-18·v1.0.0·Initial import from mobile-offline-storage·automated