Skip to main content

Documentation Index

Fetch the complete documentation index at: https://turnkey-0e7c1f5b-graham-docs-revamp.mintlify.app/llms.txt

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

Store and retrieve encryption keys from Turnkey’s secure enclave with policy-controlled access. Your infrastructure holds encrypted data and Turnkey holds the encryption key. Neither party alone can access plaintext. For an overview of Turnkey’s key management capabilities, see the Key Management Overview.

Powered by Turnkey

  • World App (by Tools for Humanity) — encrypts each user’s recovery bundle on-device and stores the encryption key in Turnkey’s secure enclave, gated by user authentication via OAuth. View on GitHub.

Key implementation decisions

DecisionWhat to considerLearn more
Export controlsGate key export through policies. Require quorum approval, restrict which users can trigger export, or scope export to specific key IDs.Policy Engine, Root Quorum
Authentication methodChoose how users authenticate to access keys: API keys, passkeys, social logins, email, or SMS OTP. Match the method to your security and UX requirements.Authentication Overview
Encrypted data storageStore encrypted bundles wherever you control: client-side (localStorage, IndexedDB), mobile secure enclave, your database, or object storage (S3, GCS). Turnkey only holds the encryption key.

Example: backup and recovery

A common pattern for applications: encrypt recovery bundles, store them in your infrastructure, and authenticate through Turnkey to decrypt when needed. No single party holds both the encrypted data and the decryption key.
2-of-2 security model diagram showing encrypted data in your infrastructure and encryption key in Turnkey secure enclave
NeedHow Turnkey solves it
Risk separation between platformsTurnkey never sees the encrypted data; your infrastructure never sees the encryption key. Both must be compromised.
Decrypt recovery bundles without managing keys directlyEncryption key lives in the enclave; authenticate to export it on demand
Caller retains controlOnly the caller’s authenticator (passkey, email, social login) can trigger key export
Policy-gated access for sensitive operationsQuorum approval, scoped export policies, and audit trails for every key operation

Implementation steps

Explore the complete implementation in the GitHub encryption-key-escrow example.
1

Create encryption keypair

Generate a P-256 keypair in Turnkey using createPrivateKeys. The private key is stored in Turnkey’s secure enclave and never exposed.
const { privateKeys } = await turnkey.apiClient().createPrivateKeys({
  privateKeys: [{
    privateKeyName: "escrow-encryption-key",
    curve: "CURVE_P256",
    addressFormats: [],
  }],
});
2

Retrieve public key

Fetch the public key to use for encryption:
const { privateKey } = await turnkey.apiClient().getPrivateKey({
  privateKeyId: encryptionKeyId,
});
const publicKey = privateKey.publicKey;
3

Encrypt data locally

Use the public key to encrypt sensitive data on your side. Turnkey never sees the plaintext or the encrypted result:
// Using P-256 ECIES encryption
const encryptedBundle = await encryptWithPublicKey(publicKey, sensitiveData);

// Store in YOUR infrastructure
await saveToYourStorage(encryptedBundle);
4

Authenticate and export decryption key

Authenticate the user through your normal auth flow, then request the encryption private key from Turnkey using exportPrivateKey:
const targetKeyPair = generateP256KeyPair();

const { exportBundle } = await turnkey.apiClient().exportPrivateKey({
  privateKeyId: encryptionKeyId,
  targetPublicKey: targetKeyPair.publicKeyUncompressed,
});

const decryptionKey = await decryptExportBundle({
  exportBundle,
  embeddedKey: targetKeyPair.privateKey,
  organizationId,
});
Use Turnkey’s policy engine to add controls on key export:
{
  "policyName": "Escrow-Key-Export-Policy",
  "effect": "EFFECT_ALLOW",
  "condition": "activity.type == 'ACTIVITY_TYPE_EXPORT_PRIVATE_KEY' && private_key.id == '<ENCRYPTION_KEY_ID>'",
  "consensus": "approvers.count() >= 2"
}
This example requires two approvers for any export of the encryption key, adding human oversight to sensitive operations.
5

Decrypt and use locally

Decrypt your stored bundles and use them locally, with no further Turnkey calls:
const plaintext = await decryptWithPrivateKey(decryptionKey, encryptedBundle);
// Use the decrypted data (sign transactions, access credentials, etc.)
When done, clear the decryption key and any decrypted data from memory:
secureWipe(decryptionKey);
secureWipe(decryptedData);

Next steps