# Work with Keystores

## Overview

A [JSON keystore](https://ethereum.org/en/developers/docs/data-structures-and-encoding/web3-secret-storage/)
(Web3 Secret Storage, version 3) stores a private key encrypted under a password-derived key —
the format used by Geth and most wallet export flows. The [`Keystore`](/api/Keystore) module
derives keys with scrypt or PBKDF2 and encrypts/decrypts private keys against them.

## Recipes

### Encrypt a Private Key

Derive an encryption key from a password with [`Keystore.pbkdf2`](/api/Keystore/pbkdf2) (or
[`Keystore.scrypt`](/api/Keystore/scrypt)), then encrypt the private key with
[`Keystore.encrypt`](/api/Keystore/encrypt).

```ts twoslash
import { Keystore, Secp256k1 } from 'ox'

const privateKey = Secp256k1.randomPrivateKey()

// Derive an encryption key from a password.
const [key, opts] = Keystore.pbkdf2({ password: 'testpassword' })

// Encrypt the private key.
const keystore = Keystore.encrypt(privateKey, key, opts)
// @log: {
// @log:   "crypto": {
// @log:     "cipher": "aes-128-ctr",
// @log:     "ciphertext": "...",
// @log:     "kdf": "pbkdf2",
// @log:     ...
// @log:   },
// @log:   "id": "...",
// @log:   "version": 3,
// @log: }
```

The resulting object is plain JSON — persist it with `JSON.stringify` and it will
interoperate with Geth, ethers, and other keystore-aware tooling.

### Decrypt a Keystore

Re-derive the decryption key from the keystore's stored KDF parameters with
[`Keystore.toKey`](/api/Keystore/toKey), then recover the private key with
[`Keystore.decrypt`](/api/Keystore/decrypt).

```ts twoslash
import { Keystore } from 'ox'

declare const keystore: Keystore.Keystore

// Derive the decryption key from the keystore & password.
const key = Keystore.toKey(keystore, { password: 'testpassword' })

// Decrypt the private key.
const privateKey = Keystore.decrypt(keystore, key)
// @log: '0x...'
```

`toKey` reads which KDF (and which parameters) the keystore was encrypted with, so the same
code handles both scrypt and PBKDF2 keystores.

### Choose scrypt vs PBKDF2

scrypt is memory-hard, making large-scale GPU/ASIC cracking expensive — it is the default in
Geth exports and the better choice for new keystores. PBKDF2 is cheaper to compute (for
attackers too) but universally supported. Both have async variants that keep the main thread
responsive.

```ts twoslash
import { Keystore, Secp256k1 } from 'ox'

const privateKey = Secp256k1.randomPrivateKey()

const [key, opts] = await Keystore.scryptAsync({ password: 'testpassword' })

const keystore = Keystore.encrypt(privateKey, key, opts)
```

See [`Keystore.scryptAsync`](/api/Keystore/scryptAsync) and
[`Keystore.pbkdf2Async`](/api/Keystore/pbkdf2Async) for tuning parameters (cost factor `n`,
iteration count).

## Best Practices

### Prefer the Async KDF Variants

Key derivation is deliberately slow. In browsers, servers, and anything interactive, use
`scryptAsync`/`pbkdf2Async`/[`toKeyAsync`](/api/Keystore/toKeyAsync) so derivation does not
block the event loop.

### The Keystore Is Only as Strong as the Password

The KDF slows brute force; it does not fix a weak password. Keep the default work factors
(2<sup>18</sup> scrypt cost / 262,144 PBKDF2 iterations) or raise them — never lower them for
convenience.

### Treat Keystore JSON as Sensitive

A keystore file is an offline-crackable target. Store it with the same care as any secret, and
zero out decrypted private keys as soon as they have served their purpose.

## See More

<Cards>
  <Card icon="lucide:key-round" title="Mnemonics & HD Wallets" description="Generate the keys you are encrypting." to="/guides/accounts/mnemonics-hd" />

  <Card icon="lucide:at-sign" title="Derive & Validate Addresses" description="Derive the address for a decrypted private key." to="/guides/accounts/addresses" />

  <Card icon="lucide:key" title="Work with AES-GCM" description="Password-encrypt arbitrary data, not just private keys." to="/guides/crypto/encryption" />
</Cards>
