Skip to content

Encrypting SQLite databases in Capacitor

Data security is paramount in mobile applications, especially when handling sensitive user information. In this guide, we'll explore how to encrypt SQLite databases in Capacitor applications using the Capacitor SQLite plugin with 256-bit AES encryption and secure key management through the Capacitor Secure Preferences plugin.

Introduction

SQLite databases in mobile applications often contain sensitive user data such as personal information, authentication tokens, or financial records. Without proper encryption, this data remains vulnerable to unauthorized access if a device is compromised. The Capacitor SQLite plugin provides robust 256-bit AES encryption capabilities, ensuring that your database remains secure even if the device falls into the wrong hands.

Combined with the Capacitor Secure Preferences plugin for secure key storage, you can implement a comprehensive encryption strategy that protects both your data and the encryption keys used to secure it.

Installation

To implement database encryption in your Capacitor application, you'll need to install and configure both the Capacitor SQLite plugin (with encryption support) and the Capacitor Secure Preferences plugin for secure key management.

Secure Preferences

The Capacitor Secure Preferences plugin provides secure storage for sensitive information like encryption keys using the Android Keystore and iOS Keychain. To install the plugin, please refer to the Installation section in the plugin documentation.

SQLite

The Capacitor SQLite plugin supports encryption through SQLCipher integration. To install the plugin with encryption support, please refer to the Installation section in the plugin documentation.

Important: Make sure to enable SQLCipher support during installation by configuring the platform-specific settings as described in the plugin documentation.

Usage

Let's walk through the essential steps to encrypt a SQLite database in your Capacitor application.

Generating the encryption key

First, you need to generate a secure encryption key. This key will be used to encrypt and decrypt the database. It is crucial to use a strong, unique key for each database instance. You have several options for generating this key:

  1. Generate a random key on the client: Use a cryptographically secure random number generator to create a 256-bit key.
  2. Generate a random key on the backend: Generate the key on your backend server and securely transmit it to the client application.
  3. Use a user-provided key: Allow users to set their own encryption key, but ensure it meets security standards (e.g., 256 bits).

As an example, here's how to generate a random key on the client using the Web Crypto API:

const generateEncryptionKey = async (): Promise<string> => {
  // Use a secure random number generator to create a 256-bit key
  const key = new Uint8Array(32); // 256 bits = 32 bytes
  window.crypto.getRandomValues(key);
  return Array.from(key).map(b => b.toString(16).padStart(2, '0')).join('');
};

This function generates a random 256-bit key and returns it as a hexadecimal string. You can call this function when you need to create a new database or change the encryption key.

Storing the encryption key

Next, you need to securely store the encryption key since it will be required every time you open the database. You can use the Capacitor Secure Preferences plugin to store the key securely on the device:

import { SecurePreferences } from '@capawesome-team/capacitor-secure-preferences';

const getEncryptionKeyFromSecurePreferences = async (): Promise<string | null> => {
  const { value } = await SecurePreferences.get({ key: 'encryptionKey' });
  return value;
};

const setEncryptionKeyInSecurePreferences = async (key: string): Promise<void> => {
  await SecurePreferences.set({ key: 'encryptionKey', value: key });
};

const getEncryptionKey = async (forceNew: boolean = false): Promise<string> => {
  // Retrieve the encryption key from secure preferences
  let encryptionKey = await getEncryptionKeyFromSecurePreferences();
  if (!encryptionKey || forceNew) {
    // Generate a new encryption key if it doesn't exist or if forced
    encryptionKey = await generateEncryptionKey();
    // Store the new key securely
    await setEncryptionKeyInSecurePreferences(encryptionKey);
  }
  return encryptionKey;
};

The getEncryptionKey(...) function retrieves the encryption key from secure preferences, generating a new one if it doesn't exist or if forced. This ensures that your key is always securely stored and easily retrievable when needed.

Encrypting the database

Now that you have a secure encryption key, you can open an encrypted SQLite database using the Capacitor SQLite plugin. For this, you'll use the open(...) method with the encryptionKey option:

import { Sqlite } from '@capawesome-team/capacitor-sqlite';

const openEncryptedDatabase = async () => {
  const encryptionKey = await getEncryptionKey();

  const { databaseId } = await Sqlite.open({
    encryptionKey,
    path: 'db.sqlite3'
  });

  return databaseId;
};

The open(...) method opens the database with the specified encryption key. Please note that it's not yet possible to encrypt an already existing database with the plugin. You must create a new database with the encryption key from the start. As a workaround, you can create a new encrypted database and then copy the data from the old unencrypted database to the new one.

Changing the encryption key

If you need to change the encryption key for an existing database, you can do so using the changeEncryptionKey(...) method. This method allows you to update the encryption key while keeping the existing data intact:

const changeKey = async (databaseId: number) => {
  const encryptionKey = await getEncryptionKey(true);

  await Sqlite.changeEncryptionKey({
    databaseId,
    encryptionKey,
  });
};

By passing true to the getEncryptionKey(...) function, you force it to generate a new key. The changeEncryptionKey(...) method updates the database with the new key, ensuring that your data remains secure.

Best Practices

Use Strong, Unique Encryption Keys

Generate cryptographically secure random keys for each database. Avoid using predictable keys based on user passwords or device identifiers. Use platform-specific secure random number generators and ensure keys are at least 256 bits in length.

Implement Key Rotation

Regularly rotate encryption keys to minimize the impact of potential key compromise. Implement a key rotation strategy that can seamlessly migrate data from old keys to new ones without data loss.

Handle Key Loss Gracefully

Design your application to handle scenarios where encryption keys are lost or corrupted. Implement backup strategies and user recovery mechanisms, while ensuring that fallback procedures don't compromise security.

Conclusion

Encrypting SQLite databases in Capacitor applications provides an essential layer of security for sensitive user data. By combining the Capacitor SQLite plugin's 256-bit AES encryption with secure key management through the Capacitor Secure Preferences plugin, you can build robust, secure mobile applications that protect user privacy and comply with modern security standards.

To stay updated with the latest updates, features, and news about the Capawesome, Capacitor, and Ionic ecosystem, subscribe to the Capawesome newsletter and follow us on X (formerly Twitter).

If you have any questions or need assistance with SQLite database encryption or database security, feel free to reach out to the Capawesome team. We're here to help you implement robust encryption strategies and secure your Ionic applications effectively.