> ## Documentation Index
> Fetch the complete documentation index at: https://www.cometchat.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Encrypt the Cache

> Encrypt the offline message cache at rest with SQLCipher, choose what happens when encryption cannot run, and inspect the live encryption status with the CometChat React Native Chat SDK.

> [accordion] AI Integration Quick Reference
>
> | Field | Value |
> | - | - |
> | 1. SQLCipher in your app | Add `{"op-sqlite": {"sqlcipher": true}}` to **your app's** `package.json`, then rebuild |
> | 2. A key store | Install `react-native-keychain`, or call `CometChat.setOfflineKeyVault(vault)` before `init` |
> | 3. Turn it on | `new CometChat.OfflineConfig({ enabled: true, encryption: true })` |
> | Failure behavior | `encryptionFailureBehavior`: `ONLINE_ONLY` (default) or `PLAINTEXT_FALLBACK` |
> | Inspect | `CometChat.getOfflineEncryptionStatus()` → `{ enabled, atRest, keyStorage, cipherAvailable, cipher, leakage }` |
>
> ```typescript theme={null}
> // package.json (your app) — then rebuild
> // "op-sqlite": { "sqlcipher": true }
>
> const offlineConfig = new CometChat.OfflineConfig({
>   enabled: true,
>   encryption: true,
>   encryptionFailureBehavior: CometChat.EncryptionFailureBehavior.ONLINE_ONLY, // default; never writes plaintext
> });
>
> const status = CometChat.getOfflineEncryptionStatus();
> status.enabled;          // what was configured
> status.atRest;           // "SQLCIPHER" or "NONE"
> status.keyStorage;       // "KEYSTORE", "SOFTWARE" or "NONE"
> status.cipherAvailable;  // is SQLCipher present in this build?
> status.cipher;           // engine cipher_version once proven, else null
> status.leakage;          // what is never covered (media files on disk)
> ```

Offline support keeps the logged-in user's messages in a local database on the device. Turn encryption on to store that database **encrypted at rest** using [SQLCipher](https://www.zetetic.net/sqlcipher/). Encryption is **opt-in** and off by default; nothing changes until you set `encryption: true`. The minimum SDK version is **4.2.0**.

> [!NOTE]
>
> Encryption covers the **message database only**. Downloaded media files (photos, voice notes, documents) stay as ordinary unencrypted files on disk, because their local paths are handed to image and video loaders. See [What gets encrypted](#what-gets-encrypted). This is not end-to-end encryption: CometChat's servers still see message content.

## Prepare your app

The SDK does **not** bundle the cipher engine or a key store, so apps that do not use encryption pay nothing for them. If you enable encryption you must provide both, on your side.

**1. Compile SQLCipher into your app**

The local database is provided by `@op-engineering/op-sqlite`. Add its SQLCipher flag to **your app's** `package.json` and rebuild the native project:

```json package.json theme={null}
{
  "op-sqlite": {
    "sqlcipher": true
  }
}
```

**2. Provide a secure key store**

Install `react-native-keychain`; the SDK detects it automatically. Alternatively, supply your own store with `CometChat.setOfflineKeyVault(vault)` before `init`. A vault implements `get(service)`, `set(service, hexKey)`, `delete(service)` and `kind()`, where `kind()` reports `"KEYSTORE"`, `"SOFTWARE"`, or `"NONE"`. Pass `null` to return to auto-detection.

> [!WARNING]
>
> If either is missing, the cache **cannot run encrypted**. What happens then is decided by [`encryptionFailureBehavior`](#choose-what-happens-when-encryption-cannot-run) — by default the session runs online-only (no local cache). The SDK never creates a database that is labelled encrypted but is not.

## Turn on encryption

Set `encryption: true` on the `OfflineConfig` you pass to the SDK during initialization (see [Enable Offline Support](https://www.cometchat.com/docs/sdk/react-native/offline-enable.md)). Like the other options, it is read once at init and fixed for the session.

> [tab] TypeScript
>
> ```typescript theme={null}
> const offlineConfig: CometChat.OfflineConfig = new CometChat.OfflineConfig({
>   enabled: true,
>   encryption: true,
>   encryptionFailureBehavior: CometChat.EncryptionFailureBehavior.ONLINE_ONLY,
> });
> ```

> [tab] JavaScript
>
> ```javascript theme={null}
> const offlineConfig = new CometChat.OfflineConfig({
>   enabled: true,
>   encryption: true,
>   encryptionFailureBehavior: CometChat.EncryptionFailureBehavior.ONLINE_ONLY,
> });
> ```

## Choose what happens when encryption cannot run

`encryptionFailureBehavior` decides what the cache does when encryption **cannot be applied at all** on this build or device — for example SQLCipher is not compiled into the app, or there is no secure key store. It governs only that case: a transient failure still retries without writing plaintext, and a lost or wrong key self-heals by wiping and rebuilding the cache **encrypted**.

| Value | Behavior | Writes plaintext? |
| - | - | - |
| `ONLINE_ONLY` *(default)* | Do not open a local cache at all — the session runs online-only. Makes a setup mistake visible: offline simply does not work until the missing piece is added. | No |
| `PLAINTEXT_FALLBACK` | Open an **unencrypted** cache so offline keeps working. | Yes |

> [!WARNING]
>
> `PLAINTEXT_FALLBACK` means cached message content may be stored **unencrypted** on disk whenever encryption cannot run. This availability-over-guarantee trade is wrong for compliance use — keep the default `ONLINE_ONLY` if cached content must never be written in the clear.

## Key storage

You do not generate or manage the encryption key. The SDK generates a random 32-byte key, one per app ID and logged-in user (UID), and keeps it in the platform keychain through `react-native-keychain`, or in the vault you supplied. The key never leaves the device and is never returned by any API. If the key is lost or the stored database no longer matches it, the cache resets and rebuilds — see [Cache resets](#cache-resets-on-key-loss).

## Check encryption status

`CometChat.getOfflineEncryptionStatus()` returns an object describing what encryption is **actually** doing — never what was merely requested. It is synchronous, safe to call at any time, and never returns the key or any part of it.

> [tab] TypeScript
>
> ```typescript theme={null}
> const status = CometChat.getOfflineEncryptionStatus();
> console.log(
>   `enabled=${status.enabled} atRest=${status.atRest} keyStorage=${status.keyStorage}` +
>   ` cipherAvailable=${status.cipherAvailable} cipher=${status.cipher}`
> );
> ```

> [tab] JavaScript
>
> ```javascript theme={null}
> const status = CometChat.getOfflineEncryptionStatus();
> console.log(
>   `enabled=${status.enabled} atRest=${status.atRest} keyStorage=${status.keyStorage}` +
>   ` cipherAvailable=${status.cipherAvailable} cipher=${status.cipher}`
> );
> ```

| Field | Type | Meaning |
| - | - | - |
| `enabled` | `boolean` | What was **configured** (`encryption`), not what was achieved. `false` while CometChat has offline switched off for your app. |
| `atRest` | `string` | Coverage actually achieved: `"SQLCIPHER"` once an encrypted open is proven, otherwise `"NONE"`. |
| `keyStorage` | `string` | `"KEYSTORE"` when the key came from the platform keychain; `"SOFTWARE"` when a vault reports a software fallback; `"NONE"` when this session holds no key. Read `NONE` as "no key in use", not "no key exists". |
| `cipherAvailable` | `boolean` | Whether the SQLCipher engine is present in this build. `false` when encryption is off or SQLCipher is missing. |
| `cipher` | `string \| null` | The engine's `cipher_version` once an encrypted open is proven, else `null`. |
| `leakage` | `string` | What encryption at rest never covers — the media files stored outside the database. |

> [!TIP]
>
> The two most useful support signals are `enabled === true` with `cipherAvailable === false` (SQLCipher is not compiled into the app — add the `op-sqlite` flag and rebuild) and `enabled === true` with `keyStorage === "NONE"` (no secure key store — install `react-native-keychain` or set a vault).

## What gets encrypted

| Data | Encrypted at rest? |
| - | - |
| Message database (messages, conversations, metadata, the pending-send outbox) | Yes, when `atRest` is `SQLCIPHER` — the whole database file, including indexes and journals |
| Downloaded media files on disk (images, video, audio, documents) | **No** — stored as ordinary files outside the database |

Media bytes live outside the database because `Attachment.getLocalPath()` is public API and those paths are passed straight to media loaders. "Encrypted at rest" here always means the message database, never the media cache.

## Cache resets on key loss

If the key can no longer decrypt the stored database, the SDK wipes the local cache and rebuilds it **encrypted** — it never silently falls back to plaintext for a cache that could be encrypted. Any unsent messages still queued in the outbox at that point are lost. They lived inside the unreadable database, so they cannot be counted or reported one by one: `pendingMessagesLost` is `-1` (`CometChat.OFFLINE_PENDING_COUNT_UNKNOWN`). When the reset is `ENCRYPTION_CHANGED` instead, each queued message fails with reason `DISCARDED` and `pendingMessagesLost` is the exact count, unless the old database could not be read.

Register an `OfflineCacheResetListener` to be told when this happens:

> [tab] TypeScript
>
> ```typescript theme={null}
> CometChat.addOfflineCacheResetListener("LISTENER_ID", {
>   onOfflineCacheReset: (reason: CometChat.OfflineCacheResetReason, pendingMessagesLost: number) => {
>     // pendingMessagesLost is -1 when the number lost is unknown, not zero.
>     console.warn("Cache reset:", reason, "pending lost:", pendingMessagesLost);
>   },
> });
> ```

> [tab] JavaScript
>
> ```javascript theme={null}
> CometChat.addOfflineCacheResetListener("LISTENER_ID", {
>   onOfflineCacheReset: (reason, pendingMessagesLost) => {
>     // pendingMessagesLost is -1 when the number lost is unknown, not zero.
>     console.warn("Cache reset:", reason, "pending lost:", pendingMessagesLost);
>   },
> });
> ```

The encryption-related reasons are `KEY_LOST`, `WRONG_KEY`, `CORRUPTED`, `ESCALATED`, and `ENCRYPTION_CHANGED` (you turned encryption on or off between launches, which also discards any queued messages and the cached media). See [Stay in Sync](https://www.cometchat.com/docs/sdk/react-native/offline-sync.md) for the full cache-reset flow and the complete list of reasons, and the [API Reference](https://www.cometchat.com/docs/sdk/react-native/offline-reference.md) for every field and enum.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.