# Database encrypted (SQLite encryption or equivalent)

- **Pattern:** `ab-001886` (`mobile-offline-storage.storage-security.database-encryption`)
- **Severity:** high
- **Lifecycle:** active
- **Last modified:** 2026-04-18
- **Canonical URL:** https://auditbuffet.com/patterns/ab-001886
- **License:** CC-BY-4.0 — attribute to AuditBuffet Pattern Catalog (https://auditbuffet.com/patterns/ab-001886)

## 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.

```tsx
// 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:

  ```tsx
  // 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):

  ```tsx
  // 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):

  ```tsx
  // 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
- cwe CWE-922
- owasp A02
- gdpr Art. 32

Taxons: cryptography-and-secrets

HTML version: https://auditbuffet.com/patterns/ab-001886
