Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/matrix-org/matrix-js-sdk/llms.txt

Use this file to discover all available pages before exploring further.

Matrix server-side secret storage (also called 4S or SSSS) is a mechanism for storing sensitive cryptographic material — such as cross-signing private keys and the key backup decryption key — encrypted on the homeserver. This allows you to access those secrets from new devices using a single recovery key.
Secret storage should be set up before calling bootstrapCrossSigning or enabling key backup, so that newly created private keys can be stored immediately.

How it works

Secret storage works by:
  1. Generating a secret storage key (the recovery key) on the client.
  2. Encrypting secrets (cross-signing keys, key backup key) with that key.
  3. Uploading the encrypted blobs to the homeserver as account data.
When a new device needs those secrets, it calls getSecretStorageKey to retrieve the recovery key from the user, which is then used to decrypt the blobs from account data. The algorithm used is m.secret_storage.v1.aes-hmac-sha2, defined in secret-storage.ts:
export const SECRET_STORAGE_ALGORITHM_V1_AES = "m.secret_storage.v1.aes-hmac-sha2";

Providing cryptoCallbacks

To access secret storage, provide getSecretStorageKey in the cryptoCallbacks option when creating the client. This callback is called whenever the crypto stack needs to decrypt something from secret storage:
import * as sdk from "matrix-js-sdk";

const matrixClient = sdk.createClient({
    baseUrl: "http://localhost:8008",
    accessToken: myAccessToken,
    userId: myUserId,
    cryptoCallbacks: {
        /**
         * Called when the crypto stack needs to access secret storage.
         * Prompt the user to enter their recovery key and return it.
         *
         * @param opts.keys - Map from key ID to SecretStorageKeyDescription.
         * @param name - The name of the secret being accessed.
         * @returns [keyId, privateKeyBytes] or null if unknown.
         */
        getSecretStorageKey: async ({ keys }, name) => {
            const recoveryKey = await promptUserForRecoveryKey();
            // Return the key ID and the raw private key bytes
            const keyId = Object.keys(keys)[0];
            return [keyId, recoveryKey];
        },

        /**
         * Optional. Called when a new secret storage key is created.
         * Use this to cache the key temporarily so getSecretStorageKey
         * doesn't have to prompt the user again in the same session.
         */
        cacheSecretStorageKey: (keyId, keyInfo, key) => {
            sessionKeyCache.set(keyId, key);
        },
    },
});
The secret storage key may be requested multiple times in quick succession during bootstrapSecretStorage. Use cacheSecretStorageKey to cache it temporarily and avoid prompting the user repeatedly.

Calling bootstrapSecretStorage

CryptoApi.bootstrapSecretStorage() is idempotent — it only sets up secret storage if it is not already configured. Call it unconditionally after initRustCrypto():
await matrixClient.initRustCrypto();

const crypto = matrixClient.getCrypto();

await crypto.bootstrapSecretStorage({
    /**
     * Called only when a new secret storage key needs to be created.
     * You MUST prompt the user to save this key, as they will need it
     * to unlock secret storage on new devices.
     */
    createSecretStorageKey: async () => {
        const key = await crypto.createRecoveryKeyFromPassphrase();
        // Display key.encodedPrivateKey to the user and ask them to save it.
        await showRecoveryKeyToUser(key.encodedPrivateKey);
        return key;
    },
});

CreateSecretStorageOpts

The bootstrapSecretStorage method accepts a CreateSecretStorageOpts object:
OptionTypeDescription
createSecretStorageKey() => Promise<GeneratedSecretStorageKey>Called when a new secret storage key is required. Return a GeneratedSecretStorageKey object.
setupNewSecretStoragebooleanIf true, creates a new secret storage key even if one already exists.
setupNewKeyBackupbooleanIf true, also calls resetKeyBackup() and stores the new key backup decryption key in secret storage.

The createSecretStorageKey callback

When secret storage has not yet been set up, bootstrapSecretStorage calls createSecretStorageKey to generate a new key. The callback must return a GeneratedSecretStorageKey:
export interface GeneratedSecretStorageKey {
    keyInfo?: {
        /** If the key was derived from a passphrase, information on that derivation. */
        passphrase?: PassphraseInfo;
        /** Optional human-readable name for the key. */
        name?: string;
    };
    /** The raw generated private key. */
    privateKey: Uint8Array<ArrayBuffer>;
    /**
     * The generated key, encoded for display to the user per
     * https://spec.matrix.org/v1.7/client-server-api/#key-representation
     */
    encodedPrivateKey?: string;
}
Use CryptoApi.createRecoveryKeyFromPassphrase() to generate the key:
const key = await crypto.createRecoveryKeyFromPassphrase();
// key.encodedPrivateKey is a human-readable string like:
// "EsTT qPME mVyg 9rIF ..."
await showAndConfirmRecoveryKey(key.encodedPrivateKey);
return key;
encodedPrivateKey must be displayed to the user and saved somewhere safe before the callback returns. There is no way to recover it later without re-running the setup flow with setupNewSecretStorage: true.

Recovery key format

The encoded recovery key is a human-readable string defined in the Matrix spec’s key representation. It looks like:
EsTT qPME mVyg 9rIF 53yT Zqn3 r9Mb 6diL
This string can be typed or copied by the user when unlocking secret storage on a new device. Recommendations for storing the recovery key:
  • Display it to the user once and prompt them to write it down or store it in a password manager.
  • Never store the raw key bytes in plaintext on the server.
  • Do not re-use the same key for multiple users.

Checking secret storage status

Use isSecretStorageReady() to check whether secret storage is fully configured, or getSecretStorageStatus() for a more detailed breakdown:
const ready = await crypto.isSecretStorageReady();
// true if secret storage is set up and contains all required secrets

const status = await crypto.getSecretStorageStatus();
// {
//   ready: boolean,
//   defaultKeyId: string | null,
//   secretStorageKeyValidityMap: { ... }
// }
console.log("Default key ID:", status.defaultKeyId);
console.log("Secrets stored:", status.secretStorageKeyValidityMap);

Forcing a new secret storage setup

To replace existing secret storage (for example, if the user has lost their recovery key), pass setupNewSecretStorage: true:
await crypto.bootstrapSecretStorage({
    setupNewSecretStorage: true,
    createSecretStorageKey: async () => {
        const key = await crypto.createRecoveryKeyFromPassphrase();
        await showRecoveryKeyToUser(key.encodedPrivateKey);
        return key;
    },
});
Using setupNewSecretStorage: true invalidates the existing recovery key. Any previously backed-up secrets will need to be re-stored with the new key.

Complete example

The following is the full pattern recommended in the matrix-js-sdk README:
const matrixClient = sdk.createClient({
    baseUrl: "http://localhost:8008",
    accessToken: myAccessToken,
    userId: myUserId,
    cryptoCallbacks: {
        getSecretStorageKey: async (keys) => {
            // This function should prompt the user to enter their secret storage key.
            return mySecretStorageKeys;
        },
    },
});

await matrixClient.initRustCrypto();

await matrixClient.getCrypto().bootstrapSecretStorage({
    // This function will be called if a new secret storage key (aka recovery key) is needed.
    // You should prompt the user to save the key somewhere, because they will need it
    // to unlock secret storage in future.
    createSecretStorageKey: async () => {
        return mySecretStorageKey;
    },
});