docs

Ease docs/Concepts/Encryption

Encryption and local state

The SDK manages every libsignal key for you. You never see a private key, never rotate one, never fan out a SKDM by hand. What you should know: what state lands on the device, what survives a sign-out, and what the SDK guarantees about message text at rest.

What you don't have to manage

  • Identity keys. Generated at sign-up. Stored under your user's encrypted libsignal store; never surfaced to your code.
  • Pre-keys and signed pre-keys. The SDK uploads a fresh bundle at sign-up, replaces consumed pre-keys on a schedule, and refreshes the signed pre-key periodically.
  • 1:1 session state. The Signal Protocol's double-ratchet runs inside the SDK on every DM send and receive. Forward secrecy and post-compromise security come from the library, not from your code.
  • Sender Key distribution. When you call room.send(...) on a group, the SDK mints a distributionId, fans the SKDM out to every member as a 1:1 message, then sends the group ciphertext. None of this is on your call path.
  • Decrypting incoming messages. The events you handle in room.on('message', ...) already carry the decoded plaintext. The SDK decrypts before dispatch.

What the SDK stores on the device

Three buckets, all scoped to the signed-in user's ID. Different lifetimes, different backends per platform.

The libsignal store

Identity keys, pre-keys, session records per peer, sender-key chains per group and distribution. Backed by a per-platform persistent store, encrypted at rest:

  • Web: IndexedDB, encrypted at rest with AES-GCM. On password tenants the key is derived from the user's password with PBKDF2; on passkey tenants it is derived from the passkey's WebAuthn PRF output. The key is held in memory for the session only; the bytes on disk are inert without it.
  • Apple: a SQLite file under Application Support, with the master key in Keychain (device-bound, Secure Enclave when available).
  • Android: Room database, master key in the Keystore (hardware-backed on modern devices).
  • .NET: platform-appropriate secure storage (Keychain on macOS, Credential Manager on Windows, libsecret on Linux).

Survives app restarts. The next ease.auth.restoreSession(saved) rehydrates this store before any send or receive runs.

DM history

Decrypted DM messages persist in a per-user store so your UI does not lose history between launches. The SDK writes the plaintext on receive and reads it on init. The store is scoped to the signed-in user.

On the web, this is localStorage by default: convenient, but readable by anything else on the origin. If your application's threat model rules out same-origin readers, persist plaintext through a store you control instead. On Apple, Android, and .NET the default backend is the platform-secure option.

Group history

Decrypted group messages live in an in-memory log. They do not survive app restarts. After a launch, the SDK replays the server's backlog on the first socket open and rebuilds the log; older messages outside the backlog window are not reachable from the device.

Sign-out clears everything

ease.auth.signOut() tears down the in-memory state and clears the local libsignal store, the DM history, and the saved session. After it resolves, the device has nothing identifying you and nothing plaintext.

If a device is lost and the password is not known, libsignal state on disk is encrypted at rest and cannot be decrypted offline. DM history on web's localStorage default is the exception: plaintext is readable by anything with filesystem access to the browser profile. Treat it the way you'd treat any browser-stored data.

The web security model

At-rest encryption protects the offline case: a stolen laptop, a copied browser profile, an exfiltrated IndexedDB file. Its strength depends on the tenant's auth method. On passkey tenants the keystore key is the passkey's PRF output, 256 bits of hardware entropy with no user-chosen component, so the stored bytes are not brute-forceable offline. On password tenants the key comes from the user's password, stretched with PBKDF2; the offline strength then tracks the strength of that password.

The other half of the model is the live page. On the web, the SDK runs inside your application's origin, and the browser does not isolate it from other code in that same origin. A browser extension the user installed, or a cross-site-scripting flaw in the page, has the same access as the SDK. This is the standard browser security model: a web application's origin is its trust boundary. This boundary is the same for every tenant; unlike the offline protection above, it does not vary with the auth method.

Two practices keep the web endpoint sound, both standard for any application handling sensitive data. First, serve the app under a strict Content-Security-Policy and escape user-controlled content consistently; that is what keeps untrusted code out of the origin. Second, match the client platforms to your threat model: where the endpoint itself must be treated as hostile, the native SDKs (Apple, Android, .NET) hold keys in the operating-system keystore, a stronger guarantee than any browser can offer. Most consumer and business applications are well served by the web SDK with those two practices in place; higher-assurance deployments add or prefer the native SDKs.

Each device holds its own keys

The libsignal store is device-bound. Link a second device to one identity through the QR device-link flow and each device keeps its own keys and ratchets its own chains; the two do not share session state. New messages encrypt to every linked device, so they reach all of them. History does not move backward: a DM sent before a device was linked was encrypted to the keys that existed then, so it is not readable on the newer device.

See also