Keys & Addresses Published Updated

Ethereum Keystore File (JSON Wallet): Structure and Security

#ethereum#wallet#private key#metamask#security
Ethereum Keystore File (JSON Wallet): Structure and Security

An Ethereum keystore file, also called a JSON wallet or a UTC file, is a small JSON document that holds one private key encrypted with a password. Its format is the Web3 Secret Storage definition, version 3: a key derivation function, scrypt or PBKDF2, stretches the password, AES-128-CTR encrypts the key, and a Keccak-256 checksum tells a wrong password from a right one. Whoever copies the file still needs the password, but a weak password can be guessed offline, and a forgotten one cannot be reset.

Keystore Files at a Glance

Ethereum keystore file
StandardWeb3 Secret Storage, version 3
HoldsOne private key, so one account, usually with its address in plain text
Password stretchingscrypt (Geth’s default) or PBKDF2-HMAC-SHA256
EncryptionAES-128-CTR
Wrong passwordRejected by the Keccak-256 MAC before anything is decrypted
File name in GethUTC--<date>--<address>
Opens inGeth, MyEtherWallet, the MetaMask browser extension, libraries such as eth-account
Backs upOne account only, unlike a seed phrase, which backs up every account of a wallet

What Is Inside a Keystore File?

Below is a real, working example. It encrypts the public test private key 7a28b5ba57c53603b0b07b56bba752f7784bf506fa95edc395f5cf6c7514fe9d from the Web3 Secret Storage specification with the password testpassword, using scrypt with Geth’s standard parameters. Never put funds on a test key.

{
  "address": "008aeeda4d805471df9b2a5b0f38a0c3bcba786b",
  "crypto": {
    "cipher": "aes-128-ctr",
    "cipherparams": {
      "iv": "b7d483dbb5ed84542dfa20b08ef05745"
    },
    "ciphertext": "614f2c44298a81a6ec31bb5127a2076c012d2431f3e911117b4940f6d8f1de40",
    "kdf": "scrypt",
    "kdfparams": {
      "dklen": 32,
      "n": 262144,
      "p": 1,
      "r": 8,
      "salt": "c906d7cdfb8cf20e81e919670e83542ca57950de83a1f364b8dc6cae3153f580"
    },
    "mac": "d0dee0c7c7bbe2c2b0959c315aa8dccc2de300be3e3cb7e1c5ddc66d397cbe9b"
  },
  "id": "4d2424cf-56e4-44a5-9005-b129b2aec863",
  "version": 3
}
FieldWhat it holdsSize for a 32-byte key
versionFormat versionalways 3
idA random UUID that identifies the file16 bytes
addressThe account address, unencrypted (optional)20 bytes (40 hex characters)
crypto.ciphertextThe private key, encrypted with AES-128-CTR32 bytes (64 hex characters)
crypto.cipherparams.ivRandom initialization vector for AES16 bytes (32 hex characters)
crypto.kdf, kdfparamsHow the password becomes a key: scrypt or PBKDF2, with a random saltsalt: 32 bytes by convention
crypto.macKeccak-256 checksum that tells a wrong password from a right one32 bytes (64 hex characters)

The ciphertext has the same length as the private key, which is why it is 64 hex characters and not more. The address is stored in lowercase without 0x; wallets display the same address with its mixed-case EIP-55 checksum.

How Does the Password Decrypt the Key?

Decryption works in three steps:

  1. The password and salt go through the key derivation function (here scrypt with N = 262,144, r = 8, p = 1), which produces a 32-byte derived key.
  2. The wallet computes Keccak-256 of the second half of the derived key followed by the ciphertext. If the result differs from mac, the password is wrong and nothing is decrypted.
  3. The first half of the derived key and the iv decrypt the ciphertext with AES-128-CTR, which returns the 32-byte private key.

Step 1 is deliberately expensive. Geth’s source code describes its standard scrypt settings as needing 256 MB of memory and about a second of CPU time on a modern processor (passphrase.go); in our test, a pure-JavaScript scrypt took about 0.4 seconds per attempt on an Apple M4 Pro. Every password guess pays that cost, which slows an attacker down but does not save a short or common password. Geth’s --lightkdf option writes much cheaper settings, N = 4,096 and p = 6, which make guessing far faster.

Scrypt or PBKDF2: Which KDF Does a File Use?

The crypto.kdf field says. Geth writes scrypt, while the specification names PBKDF2-HMAC-SHA256 as the function every implementation must support. This is the specification’s PBKDF2 test vector; the password testpassword decrypts it to the same test key:

{
  "crypto": {
    "cipher": "aes-128-ctr",
    "cipherparams": {
      "iv": "6087dab2f9fdbbfaddc31a909735c1e6"
    },
    "ciphertext": "5318b4d5bcd28de64ee5559e671353e16f075ecae9f99c7a79a38af5f869aa46",
    "kdf": "pbkdf2",
    "kdfparams": {
      "c": 262144,
      "dklen": 32,
      "prf": "hmac-sha256",
      "salt": "ae3cd4e7013836a3df6bd7241b12db061dbe2c6785853cce422d148a624ce0bd"
    },
    "mac": "517ead924a9d0dc3124507e3393d175ce3ff7c1e96529c6c555ce9e51205e9b2"
  },
  "id": "3198bc9c-6672-5ab3-d995-4942343ae5b6",
  "version": 3
}

The cost here is the iteration count c: 262,144 rounds of HMAC-SHA256. Unlike scrypt, PBKDF2 needs almost no memory, so it is cheaper to attack with graphics cards; we checked that both example files decrypt to the published key.

Which Wallets Create or Open Keystore Files?

The format is the same everywhere; only the file name and the KDF settings differ:

  • Geth keeps every account as a keystore file in the keystore folder of its data directory. Its account management guide gives the naming format UTC--<date>--<address> and says it is safe to copy individual key files between Ethereum nodes.
  • MyEtherWallet can create a keystore file as one of its software wallet options and can open one to access a wallet.
  • MetaMask imports a keystore file only in the browser extension: open the account selector, choose Add wallet, then Import an account, set Select Type to JSON File, choose the file and enter the password that protects the file, not your MetaMask password (MetaMask: use an existing wallet). The mobile app cannot. MetaMask also notes that imported accounts do not come back when you restore from your Secret Recovery Phrase, so keep the file and its password, or move the funds to an account derived from your phrase. MetaMask does not keep its own accounts in this format: its backup is the Secret Recovery Phrase, and it exports a single account as a raw private key; see how to export a private key from MetaMask and other wallets.

Validator Keystores Are a Different Format

Keystores from Ethereum staking follow EIP-2335, which stores the BLS12-381 keys that validators sign with. The structure looks similar, but the version is 4, the checksum uses SHA-256 instead of Keccak, the file adds fields such as pubkey and path, and the password is normalized to NFKD with control characters removed. These files belong in validator software, not in a wallet like MetaMask.

Code Example: Encrypting and Decrypting in Python

The eth-account library reads and writes keystore files:

from eth_account import Account

private_key = "0x7a28b5ba57c53603b0b07b56bba752f7784bf506fa95edc395f5cf6c7514fe9d"  # public test key

keystore = Account.encrypt(private_key, "testpassword")  # scrypt by default
restored = Account.decrypt(keystore, "testpassword")

print(keystore["crypto"]["kdf"])          # scrypt
print(restored.hex())                     # 7a28b5ba57c5...fe9d
print(Account.from_key(restored).address) # 0x008AeEda4D805471dF9b2A5B0f38A0C3bCBA786b

Every call to encrypt picks a new salt and IV, so the same key and password give a different file each time, and all of them decrypt to the same key. Account.decrypt is also how you get the raw private key out of a keystore file offline.

How Secure Is a Keystore File?

  • The password is the whole defense. Anyone who copies the file can test passwords offline for as long as they like. A long, random passphrase, such as several unrelated words, puts that out of practical reach; a short or reused one does not.
  • There is no reset. Geth’s guide warns that a lost password cannot be retrieved and the account stays locked forever. Keep the password somewhere safe and separate from the file.
  • File plus password equals the key. Together they control the account on every EVM network at once, so treat them like a private key: never upload the file to a website, and remember that nobody legitimate, including wallet support, asks for your keystore file and its password (how phishing works).
  • One key, one account. A keystore backs up a single account, while a seed phrase recreates every account a wallet derives from it; the difference is explained in seed phrase vs private key. If an account was derived from your phrase, the phrase recreates its key at any time, so the keystore is only a convenience; the seed phrase to private key converter shows that derivation on Ethereum’s standard path.
  • Old copies keep the old password. Changing the password writes a new file, but any earlier copy still opens with the old one. Delete or destroy copies you no longer trust.

Bitcoin has a similar format for a single key: BIP38 encrypts one private key with a passphrase into a string starting with 6P.

Frequently Asked Questions

Can I open a keystore file without the password?

No. The private key is encrypted with a key derived from the password, and no wallet or service can reset it. The only way back is to find the password, for example by testing variations you might have used.

Is a UTC file the same as a keystore file?

Yes. Geth names keystore files UTC--<date>--<address>, so wallets and guides call the same thing a UTC file, a JSON file or a keystore file.

Does a keystore file contain my seed phrase?

No. A version 3 keystore holds a single private key and no seed phrase, so it restores one account, not a whole wallet.

How do I change the password of a keystore file?

Decrypt it with the old password and encrypt the key again with the new one. Geth does this with geth account update <address>, which its command help describes as the way to change an account’s password; in Python, pass the result of Account.decrypt to Account.encrypt.