> ## 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.

# Secret storage

> Set up Matrix server-side secret storage (4S) in matrix-js-sdk to securely store cross-signing keys and key backup decryption keys.

Matrix [server-side secret storage](https://spec.matrix.org/v1.12/client-server-api/#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**.

<Note>
  Secret storage should be set up before calling `bootstrapCrossSigning` or enabling key backup, so that newly created private keys can be stored immediately.
</Note>

## 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`:

```typescript theme={null}
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:

```javascript theme={null}
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);
        },
    },
});
```

<Tip>
  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.
</Tip>

## Calling bootstrapSecretStorage

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

```javascript theme={null}
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:

| Option                   | Type                                       | Description                                                                                              |
| ------------------------ | ------------------------------------------ | -------------------------------------------------------------------------------------------------------- |
| `createSecretStorageKey` | `() => Promise<GeneratedSecretStorageKey>` | Called when a new secret storage key is required. Return a `GeneratedSecretStorageKey` object.           |
| `setupNewSecretStorage`  | `boolean`                                  | If `true`, creates a new secret storage key even if one already exists.                                  |
| `setupNewKeyBackup`      | `boolean`                                  | If `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`:

```typescript theme={null}
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:

<CodeGroup>
  ```javascript Random key theme={null}
  const key = await crypto.createRecoveryKeyFromPassphrase();
  // key.encodedPrivateKey is a human-readable string like:
  // "EsTT qPME mVyg 9rIF ..."
  await showAndConfirmRecoveryKey(key.encodedPrivateKey);
  return key;
  ```

  ```javascript Passphrase-derived key theme={null}
  const passphrase = await promptUserForPassphrase();
  const key = await crypto.createRecoveryKeyFromPassphrase(passphrase);
  await showAndConfirmRecoveryKey(key.encodedPrivateKey);
  return key;
  ```
</CodeGroup>

<Warning>
  `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`.
</Warning>

## Recovery key format

The encoded recovery key is a human-readable string defined in the Matrix spec's [key representation](https://spec.matrix.org/v1.7/client-server-api/#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:

```javascript theme={null}
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`:

```javascript theme={null}
await crypto.bootstrapSecretStorage({
    setupNewSecretStorage: true,
    createSecretStorageKey: async () => {
        const key = await crypto.createRecoveryKeyFromPassphrase();
        await showRecoveryKeyToUser(key.encodedPrivateKey);
        return key;
    },
});
```

<Warning>
  Using `setupNewSecretStorage: true` invalidates the existing recovery key. Any previously backed-up secrets will need to be re-stored with the new key.
</Warning>

## Complete example

The following is the full pattern recommended in the `matrix-js-sdk` README:

```javascript theme={null}
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;
    },
});
```
