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

# Password encryption

> Add password protection to embedded wallets for enhanced security

> [!WARNING]
>
> If the user loses their password, they will lose access to their wallet. There is no way to recover the password or the wallet without it.

Password encryption adds an extra layer of security to embedded wallets by requiring a password to decrypt the user's key share. This ensures that even if an attacker gains access to the stored key share, they cannot use it without the password.

## How it works

When password encryption is enabled, it specifically protects the **client-side key share**:

1. The user's client key share is encrypted with their password.
2. This encrypted share is sent through an **encryption proxy** which adds a second layer of encryption before storing it on Dynamic's servers. This ensures the share is **double-encrypted** and accessible from any device.
3. Operations like signing transactions require the password to decrypt this share once per session; after unlocking, the user is not prompted again until the next session.
4. The password is never sent to Dynamic; decryption happens entirely client-side.
5. No single party (Dynamic, encryption provider) can access the key share alone.

## Requiring password for wallets

You can require all wallets to have a password in the Dynamic Console:

1. Navigate to **Embedded Wallets**.
2. Toggle **Require Password** to enforce that all wallets must be created with a password.

> [!NOTE]
>
> When **Require Password** is enabled, a password must be provided when creating a wallet. Attempting to create a wallet without a password will return an error. Once the wallet is created and unlocked, it remains unlocked for the rest of the session. The password is not required for every operation.

## Creating a password-protected wallet

Use the `createWaasWalletAccounts` method with a `password` parameter:

```javascript theme={"system"}
import { createWaasWalletAccounts } from '@dynamic-labs-sdk/client/waas';

const createSecureWallet = async (password) => {
  await createWaasWalletAccounts({
    chains: ['EVM'],
    password
  });

  console.log('Password-protected wallet created');
};
```

## Creating a wallet with a developer-provided salt

You can supply the `password` yourself instead of collecting it from the user. This is useful when you want to encrypt a wallet with a password derived from a user or wallet specific salt that you control.

Derive the password on your backend using a key derivation function such as PBKDF2, scrypt, or Argon2id, then pass the result to the SDK. The salt and the secret used for derivation must be the same every time you unlock the wallet.

> [!WARNING]
>
> For this pattern to add security, gate the endpoint that returns the derived password with your own authentication using [Bring Your Own Auth](https://docs.dynamic.xyz/docs/javascript/authentication-methods/external-jwt.md). If you rely on Dynamic authentication instead, a Dynamic JWT alone is enough to fetch the password and decrypt the key share, which removes the extra layer that the password is meant to add.

> [!NOTE]
>
> The example below is illustrative. In production, derive the password inside a KMS or HSM (for example AWS KMS, Google Cloud KMS, or HashiCorp Vault transit) so no single service or environment variable holds a secret that can regenerate every wallet password. Store only the per-user or per-wallet salt in your database, never expose the derivation material to the browser, and remember that losing the salt or the key means the wallet cannot be unlocked.

```javascript theme={"system"}
import { createWaasWalletAccounts } from '@dynamic-labs-sdk/client/waas';

const createWalletWithSalt = async (userId) => {
  // Fetch the password from your backend.
  // Your backend derives it from a per-user salt and a server-side secret.
  const response = await fetch('/api/wallet-password', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ userId }),
    credentials: 'include'
  });

  const { password } = await response.json();

  await createWaasWalletAccounts({
    chains: ['EVM'],
    password
  });

  console.log('Wallet created with developer-provided password');
};
```

### Backend derivation example

```javascript theme={"system"}
import { pbkdf2Sync, randomBytes } from 'crypto';

// Generate a unique salt per user and store it in your database.
const userSalt = randomBytes(32).toString('hex');

// Keep this value in a secret manager; never expose it to the client.
const serverSecret = process.env.WALLET_PASSWORD_SECRET;

const derivePassword = (salt, secret) => {
  return pbkdf2Sync(secret, salt, 600000, 32, 'sha256').toString('hex');
};

const password = derivePassword(userSalt, serverSecret);
```

Use the same derived `password` when you call `unlockWallet` later in the session.

## Password management

* Users set their own password during wallet creation
* They must enter the password to unlock the wallet
* **Important: If the user forgets their password, wallet recovery is not possible without a backup.**

## Unlocking a wallet

Before performing operations with a password-protected wallet, unlock it using the `unlockWallet` method.

> [tab] JavaScript
>
> ```javascript theme={"system"}
> import { unlockWallet } from '@dynamic-labs-sdk/client/waas';
>
> const handleUnlock = async (walletAccount, password) => {
>   await unlockWallet({ walletAccount, password });
>   console.log('Wallet unlocked for this session');
> };
> ```

> [tab] React
>
> ```tsx theme={"system"}
> import { unlockWallet } from '@dynamic-labs-sdk/client/waas';
> import { useState } from 'react';
>
> function UnlockWalletForm({ walletAccount }) {
>   const [password, setPassword] = useState('');
>
>   const handleUnlock = async () => {
>     await unlockWallet({ walletAccount, password });
>     setPassword('');
>   };
>
>   return (
>     <div>
>       <input
>         type="password"
>         value={password}
>         onChange={(e) => setPassword(e.target.value)}
>         placeholder="Enter wallet password"
>       />
>       <button onClick={handleUnlock}>Unlock</button>
>     </div>
>   );
> }
> ```

Once unlocked, the wallet remains unlocked for the rest of the user session (until logout). The user is only asked for the password once per session.

Unlocking one wallet unlocks all wallets associated with the user account. As a result, all wallets for a user must share the same password.

## Operations requiring password

Any operation that changes the underlying key shares requires the password to be provided again, even if the wallet is currently unlocked. These operations include:

* **Refreshing shares**
* **Resharing** (Cloud backup)
* **Delegation**

This is because the new shares generated during these processes must be encrypted with the password before they are stored.

> [!WARNING]
>
> Dynamic's UI does not prompt for the password during these operations. You must implement refresh, reshare (e.g. cloud backup), and delegation **headlessly**: obtain the password in your own flow and pass it programmatically into the SDK when triggering these operations. Do not rely on Dynamic to show a password field for share-changing operations.

## Setting a password on an existing account

If a wallet was created without a password, you can add password protection later using `setWaasWalletAccountPassword`. This is useful when you want to enable password encryption for users who initially created their wallet without one.

```javascript theme={"system"}
import { setWaasWalletAccountPassword } from '@dynamic-labs-sdk/client/waas';

const addPasswordToWallet = async (walletAccount, newPassword) => {
  await setWaasWalletAccountPassword({
    walletAccount,
    password: newPassword
  });

  console.log('Password added to existing wallet');
};
```

## Migrating existing wallets

Enabling **Require Password** in the Dynamic Console only enforces password protection for **new** wallets. Existing wallets created before this setting was enabled remain unprotected.

To migrate an existing wallet to password protection:

1. Check if the wallet is already password-protected using `getWalletRecoveryState`.
2. If `isPasswordEncrypted` is false, prompt the user for a password and set it using `setWaasWalletAccountPassword`.

```javascript theme={"system"}
import { getWalletRecoveryState, setWaasWalletAccountPassword } from '@dynamic-labs-sdk/client/waas';

const migrateWallet = async (walletAccount, newPassword) => {
  // 1. Check if already encrypted
  const { isPasswordEncrypted } = await getWalletRecoveryState({ walletAccount });

  if (isPasswordEncrypted) {
    console.log('Wallet is already password protected');
    return;
  }

  // 2. Set password if not encrypted
  await setWaasWalletAccountPassword({
    walletAccount,
    password: newPassword
  });

  console.log('Wallet migrated to password protection');
};
```

## Updating the password

Change an existing password using `updateWaasPassword`. This will update the password for all wallets associated with the user account.

```javascript theme={"system"}
import { updateWaasPassword } from '@dynamic-labs-sdk/client/waas';

const changePassword = async (walletAccount, currentPassword, newPassword) => {
  await updateWaasPassword({
    walletAccount,
    currentPassword,
    newPassword
  });

  console.log('Password updated');
};
```

## Migrating a signer share from a temporary password

When you add an end-user signer to a Business Account wallet, use `targetSignerPassword` to encrypt the new signer share. Deliver this temporary password to the signer through an approved secure channel.

```javascript theme={"system"}
import { addBusinessAccountSigner } from '@dynamic-labs-sdk/client/waas';

await addBusinessAccountSigner({
  businessAccountId,
  targetIdentity: {
    identifier: signerEmail,
    identifierType: 'email'
  },
  signerType: 'endUser',
  targetSignerPassword,
  walletAccount
});
```

When the signer already has other password-encrypted wallets, those wallets use the signer's existing password while the new share uses `targetSignerPassword`. Pass the temporary password in a wallet-specific `passwordOverrides` entry. The update still applies to all wallets associated with the signer and keeps them synchronized under `newPassword`.

```javascript theme={"system"}
import { updateWaasPassword } from '@dynamic-labs-sdk/client/waas';

await updateWaasPassword({
  walletAccount: newlyAddedSignerWallet,
  currentPassword: userPassword,
  newPassword: userPassword,
  passwordOverrides: [
    {
      walletId: newlyAddedSignerWallet.verifiedCredentialId,
      existingPassword: targetSignerPassword
    }
  ]
});
```

Use the wallet's `verifiedCredentialId` for `walletId`. Do not use `setWaasWalletAccountPassword` for the new share because it is already password-encrypted. Keep the temporary password available until the update succeeds, and never log or transmit it through an unsecured channel.

## Checking wallet lock state

You can check if a wallet is currently locked or ready to use with `getWalletRecoveryState`. This is more accurate than just checking if a password exists, as it tells you if the wallet is currently unlocked for the session.

```javascript theme={"system"}
import { getWalletRecoveryState } from '@dynamic-labs-sdk/client/waas';

const checkRecoveryState = async (walletAccount) => {
  const { walletReadyState, isPasswordEncrypted } = await getWalletRecoveryState({ walletAccount });

  if (walletReadyState === 'encrypted') {
    console.log('Wallet is locked. User needs to enter a password.');
  } else if (walletReadyState === 'ready') {
    console.log('Wallet is unlocked and ready to use.');
  }
};
```

## Complete example

This example demonstrates how to check the wallet state, unlock it if necessary, and then sign a message.

```javascript theme={"system"}
import { signMessage } from '@dynamic-labs-sdk/client';
import { getWalletRecoveryState, unlockWallet } from '@dynamic-labs-sdk/client/waas';

const signWithPassword = async (walletAccount, message, password) => {
  // 1. Check if wallet is locked
  const { walletReadyState } = await getWalletRecoveryState({ walletAccount });

  if (walletReadyState === 'encrypted') {
    // 2. Unlock if needed
    if (!password) {
      throw new Error('Password required to unlock wallet');
    }

    await unlockWallet({ walletAccount, password });
    console.log('Wallet unlocked');
  }

  // 3. Sign message (wallet is now ready)
  const { signature } = await signMessage({
    walletAccount,
    message
  });

  return signature;
};
```

## Security considerations

> [!WARNING]
>
> Password-protected wallets are only as secure as the password itself. Enforce strong password requirements and educate users about password security.

* Password strength: Require minimum length and complexity
* **No password recovery:** If using user-provided passwords, there's no way to recover a forgotten password without a backup.
* Session-based unlock: Wallets remain unlocked for the session, reducing friction while maintaining security

## Related

* [Password encryption (React Native)](https://docs.dynamic.xyz/docs/react-native/wallets/embedded-wallets/mpc/password-encryption.md) - React Native guide


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