Persist

End-to-End Encryption

Enable P2P encryption and zero-knowledge data protection in Persist stores.

End-to-End Encryption

Enable encryption

import { createStore } from "@cuitty/persist";

const store = await createStore({
  name: "my-app",
  adapter: "sqlite",
  path: "./data/my-app.db",
  encrypt: true,
});

When encrypt: true is set, all data is encrypted on the device before it is written to disk or sent over the network. The sync server, storage backend, and any intermediary never see plaintext.

How it works

Persist generates a 256-bit device key on first run and stores it in the OS keychain (or a local keyfile as fallback). Every record is encrypted with AES-256-GCM using a per-record nonce derived from the key and the record’s path.

plaintext --> AES-256-GCM encrypt --> ciphertext --> store / sync

Reads reverse the process transparently. Application code works with plain objects — encryption and decryption are invisible to the caller.

Key management

Device keys

Each device generates its own key on first use. The key never leaves the device unless explicitly exported.

// Export the device key (for backup or migration)
const exportedKey = await store.crypto.exportKey();

// Import a key on a new device
const store = await createStore({
  name: "my-app",
  adapter: "sqlite",
  path: "./data/my-app.db",
  encrypt: true,
  key: exportedKey,
});

Key rotation

Rotate the encryption key without downtime. Persist re-encrypts existing records in the background.

await store.crypto.rotateKey();

After rotation the old key is kept in a sealed keyring so previously-synced peers can still decrypt historical data until they receive the new key.

Encrypted sync

When encryption and sync are both enabled, peers exchange ciphertext. Decryption happens only on the receiving device using a shared key.

const store = await createStore({
  name: "my-app",
  adapter: "sqlite",
  path: "./data/my-app.db",
  encrypt: true,
  sync: {
    remote: "postgres",
    strategy: "last-write-wins",
  },
});

Sharing keys between peers

Peers use a Diffie-Hellman key exchange to establish a shared secret. No key material is transmitted in the clear.

// On device A: generate an invite
const invite = await store.crypto.createInvite();
// --> send `invite.code` to device B out-of-band

// On device B: accept the invite
await store.crypto.acceptInvite(invite.code);

Once paired, both devices derive the same data key and can decrypt each other’s records.

Zero-knowledge architecture

The sync server stores only ciphertext and opaque metadata (record size, sync timestamps). It cannot:

Even if the server is compromised, an attacker gains no usable data without the device keys.