Dexie Encryption Middleware

raw JSON →
2.0.0 verified Thu Apr 23 auth: no javascript

dexie-encrypted provides a robust middleware solution for encrypting and decrypting data stored in Dexie.js (IndexedDB wrapper) databases. It transparently handles cryptographic operations, ensuring sensitive information is stored securely at rest within the browser's persistent storage. The current stable version, 2.0.0, is specifically designed for compatibility with Dexie v3.0.0 and newer versions. This library typically follows the release cadence of major Dexie versions and security patches, focusing on stability and integration. Its primary differentiator is its deep integration with Dexie's hook system, allowing developers to add encryption to their existing Dexie projects with minimal code changes by simply importing the library and configuring the encryption key and tables.

error TypeError: Cannot read properties of undefined (reading 'encrypt') or Property 'encryptedTables' does not exist on type 'Dexie'.
cause The `dexie-encrypted` side-effect import was either missed or placed incorrectly, preventing the Dexie prototype from being extended.
fix
Ensure import 'dexie-encrypted'; is present at the top level of your application entry file or before any Dexie database instance is created and initialized.
error Error: Encryption key missing for table 'yourTableName'.
cause The `db.encryptionKey` was not set, or it was `null`/`undefined` when an operation on an encrypted table was attempted.
fix
Call db.encryptionKey = await getYourSecureKey(); and ensure a valid CryptoKey instance is assigned before any read/write operations on encrypted tables.
error Uncaught (in promise) DOMException: The key 'id' in 'yourTableName' is not unique.
cause Attempting to add or put an item with a duplicate primary key (e.g., `id`) into an encrypted table without proper handling, or the encryption/decryption process is interfering with key uniqueness.
fix
Verify your schema's primary key (id) and indexes are correctly defined. If using client-side generated UUIDs, ensure they are truly unique. Review Dexie's documentation on unique keys and compound indexes.
breaking Version 2.0.0 of `dexie-encrypted` introduces breaking changes by requiring Dexie.js v3.0.0 or later. Projects using Dexie v2.x must upgrade Dexie before upgrading `dexie-encrypted`.
fix Ensure your project's `package.json` specifies `"dexie": "^3.0.0"` or higher and install dependencies.
gotcha Proper encryption key management is critical. Storing the encryption key directly in application source code, local storage, or session storage is generally insecure for production environments.
fix Implement a secure key management strategy, potentially involving Web Workers, IndexedDB (with caution), or derived keys from user passwords using strong KDFs (Key Derivation Functions) like PBKDF2 or scrypt, never storing the raw key directly.
gotcha Changes to your Dexie database schema, particularly adding or removing encrypted fields, require careful migration planning to avoid data loss or corruption. Existing encrypted data might become unreadable if the schema changes without a proper migration strategy.
fix When modifying schemas, especially for encrypted tables, implement Dexie's `version().upgrade()` methods to handle data transformation and re-encryption if necessary, ensuring backward compatibility or a clear upgrade path.
gotcha The `db.encryptionKey` must be set *before* performing any database operations on encrypted tables. Forgetting to set the key or setting it too late will result in errors when trying to read or write encrypted data.
fix Always ensure that `db.encryptionKey = await getYourSecureKey();` is called and awaited before any `db.table.add()`, `db.table.put()`, or `db.table.get()` calls that involve encrypted tables.
npm install dexie-encrypted
yarn add dexie-encrypted
pnpm add dexie-encrypted

Demonstrates initializing a Dexie database with `dexie-encrypted` middleware, setting an encryption key, and performing encrypted `add` and `get` operations for a user record.

import { Dexie } from 'dexie';
import 'dexie-encrypted'; // This extends the Dexie prototype

// In a real application, get your key securely, e.g., from a Web Worker or IndexedDB
// DO NOT store it directly in your main application code or local storage.
const getEncryptionKey = async (): Promise<CryptoKey> => {
  // For demonstration: generate a new key if not already stored in sessionStorage
  let storedKey = sessionStorage.getItem('myAppEncryptionKey');
  if (storedKey) {
    return crypto.subtle.importKey(
      'jwk',
      JSON.parse(storedKey),
      { name: 'AES-GCM', length: 256 },
      true,
      ['encrypt', 'decrypt']
    );
  }

  const newKey = await crypto.subtle.generateKey(
    { name: 'AES-GCM', length: 256 },
    true,
    ['encrypt', 'decrypt']
  );
  sessionStorage.setItem(
    'myAppEncryptionKey',
    JSON.stringify(await crypto.subtle.exportKey('jwk', newKey))
  );
  return newKey;
};

class MyEncryptedDatabase extends Dexie {
  users!: Dexie.Table<{ id: number, name: string, secret: string }, number>;

  constructor() {
    super('MyEncryptedDatabase');
    this.version(1).stores({
      users: '++id, name, secret'
    });
    // Specify which tables contain encrypted data
    this.encryptedTables(['users']);
  }
}

async function runEncryptionDemo() {
  const db = new MyEncryptedDatabase();
  
  try {
    // Set the encryption key. This must be done BEFORE any data operations.
    db.encryptionKey = await getEncryptionKey();

    // Add an encrypted user
    const userId = await db.users.add({ id: 1, name: 'Alice', secret: 'Top secret info' });
    console.log(`Added user with ID: ${userId}`);

    // Retrieve the user. It will be decrypted automatically.
    const user = await db.users.get(userId);
    console.log('Retrieved user:', user);
    console.log('User secret (decrypted):', user?.secret);

    // Verify that the retrieved secret matches the original
    if (user?.secret === 'Top secret info') {
      console.log('Encryption and decryption successful!');
    } else {
      console.error('Decryption failed or data mismatch.');
    }

  } catch (error) {
    console.error('Error during encryption demo:', error);
  } finally {
    db.close();
  }
}

runEncryptionDemo();