Encrypt Shares
Learn how to encrypt wallet backup shares using AES-256-GCM, securely manage encryption keys, and protect recovery data with passphrases, passkeys, or KMS integrations.
The SDK does not encrypt backup shares for you. You are responsible for encrypting them before storage. This page shows recommended encryption patterns.
AES-256-GCM (Recommended)
AES-256-GCM provides authenticated encryption — the ciphertext cannot be tampered with undetected.
import { createCipheriv, createDecipheriv, randomBytes } from "crypto";
/**
* Encrypt a share string with a 32-byte key.
* Returns a base64-encoded blob: iv (12 bytes) + auth tag (16 bytes) + ciphertext.
*/
function encryptShare(share: string, key: Buffer): string {
const iv = randomBytes(12);
const cipher = createCipheriv("aes-256-gcm", key, iv);
const encrypted = Buffer.concat([
cipher.update(share, "utf8"),
cipher.final(),
]);
const tag = cipher.getAuthTag();
return Buffer.concat([iv, tag, encrypted]).toString("base64");
}
/**
* Decrypt a share string encrypted with encryptShare().
*/
function decryptShare(ciphertext: string, key: Buffer): string {
const buf = Buffer.from(ciphertext, "base64");
const iv = buf.subarray(0, 12);
const tag = buf.subarray(12, 28);
const encrypted = buf.subarray(28);
const decipher = createDecipheriv("aes-256-gcm", key, iv);
decipher.setAuthTag(tag);
return Buffer.concat([
decipher.update(encrypted),
decipher.final(),
]).toString("utf8");
}Key Derivation from a Passphrase
If the encryption key comes from a user passphrase, use PBKDF2 or Argon2:
import { pbkdf2Sync, randomBytes } from "crypto";
function deriveKey(passphrase: string, salt: Buffer): Buffer {
return pbkdf2Sync(
passphrase,
salt,
600_000, // iterations — OWASP 2023 recommendation
32, // key length in bytes
"sha256"
);
}
// Encrypt
const salt = randomBytes(16);
const key = deriveKey(userPassphrase, salt);
const ciphertext = encryptShare(backup.SECP256K1.share, key);
// Store salt alongside ciphertext — you need it for decryption
await db.saveBackup(userId, {
salt: salt.toString("hex"),
ciphertext,
backupSharePairId: backup.SECP256K1.id,
});
// Decrypt
const storedSalt = Buffer.from(record.salt, "hex");
const keyAgain = deriveKey(userPassphrase, storedSalt);
const share = decryptShare(record.ciphertext, keyAgain);Key Management Options
| Approach | Pros | Cons |
|---|---|---|
| User passphrase + PBKDF2 | User controls key, no backend dependency | Weak if passphrase is weak |
| Passkey (WebAuthn) | Phishing-resistant, hardware-backed | Browser/device dependent |
| AWS KMS / GCP KMS | Centrally managed, auditable | Backend dependency |
| Hardware Security Module | Highest security | Complex, expensive |
What to Store
After encrypting, you store:
- The ciphertext (the encrypted backup share)
- The salt or key identifier needed for decryption
- The
backupSharePairId(returned frombackupWallet()) — needed when callingstoreEncryptedBackupShare()or during recovery
Never store the plaintext share or the raw encryption key alongside the ciphertext.