Skip to content

How Ethereum wallet hashes are extracted

A technical walkthrough of how Hash Extractor parses Ethereum keystore v3 JSON files to produce a crackable $ethereum$ hash for hashcat and John the Ripper.

Published on 4 min read

Hash Extractor parses Ethereum keystore files entirely in the browser — no upload, no server — and emits the $ethereum$ hash that hashcat and John the Ripper understand. This post walks through exactly how that parsing works, from the raw JSON to the final hash string.

What is an Ethereum keystore file?

Ethereum clients such as geth and tools such as MyEtherWallet store private keys in a format called a keystore v3 file — a plain JSON document that encrypts the private key with a user-supplied password. The file is typically named after the wallet's UUID and is exported by every major Ethereum client and web wallet.

At its core the file holds a top-level crypto object (or Crypto in older clients) with four relevant fields:

FieldDescription
ciphertextThe AES-128-CTR encrypted private key (hex)
macKeccak-256 of the last 16 bytes of the derived key concatenated with ciphertext (hex)
kdfKey derivation function — either "scrypt" or "pbkdf2"
kdfparamsKDF-specific parameters including salt (hex)

Detection is intentionally lightweight: the parser checks that the first non-whitespace byte is { and that both "ciphertext" and "kdfparams" appear somewhere in the first 2 KB of the file.

The MAC as the crack target

The password is never stored directly. Instead, the KDF derives a 32-byte key from the password and salt. The last 16 bytes of that derived key are concatenated with ciphertext, and Keccak-256 is computed over the result. That digest is the mac value recorded in the file.

A cracker reproduces this derivation for each candidate password and compares the resulting MAC. Because the MAC check is cheap to evaluate once the KDF output is known, the bottleneck is entirely in the KDF — by design.

KDF 1: scrypt

scrypt is the default KDF in geth-generated keystores and is deliberately memory-hard. The relevant kdfparams fields are n (CPU/memory cost), r (block size), p (parallelisation), and salt.

Hash format

$ethereum$s*<n>*<r>*<p>*<salt>*<ciphertext>*<mac>

Real example (from the extractor test suite)

Keystore fragment:

{
  "crypto": {
    "cipher": "aes-128-ctr",
    "ciphertext": "aabbccdd",
    "kdf": "scrypt",
    "kdfparams": { "dklen": 32, "n": 262144, "p": 1, "r": 8, "salt": "deadbeef" },
    "mac": "feedface"
  }
}

Extracted hash:

$ethereum$s*262144*8*1*deadbeef*aabbccdd*feedface

The field order in the hash is n, then r, then p — matching the hashcat mode 15700 specification. An n of 262144 (2^18) is the geth default and requires 256 MB of memory per candidate, which makes exhaustive cracking impractical without a focused wordlist or rule set.

KDF 2: PBKDF2

Some older or alternative clients use PBKDF2-HMAC-SHA256 instead of scrypt. The only additional parameter is c, the iteration count.

Hash format

$ethereum$p*<c>*<salt>*<ciphertext>*<mac>

Real example (from the extractor test suite)

Keystore fragment:

{
  "Crypto": {
    "ciphertext": "1122",
    "kdf": "pbkdf2",
    "kdfparams": { "c": 262144, "dklen": 32, "prf": "hmac-sha256", "salt": "cafe" },
    "mac": "babe"
  }
}

Extracted hash:

$ethereum$p*262144*cafe*1122*babe

Note that the parser accepts both "crypto" and "Crypto" as the field name — this handles the capitalisation inconsistency found in MyEtherWallet and other older clients.

Cracking with hashcat and John the Ripper

hashcat

KDFMode
scrypt-m 15700
PBKDF2-m 15600

Quick-start one-liner for a scrypt hash:

echo '$ethereum$s*262144*8*1*deadbeef*aabbccdd*feedface' > hash.txt
hashcat -m 15700 hash.txt /usr/share/wordlists/rockyou.txt

For PBKDF2, swap -m 15700 for -m 15600. Both modes support rules and masks in the usual way.

John the Ripper

john --format=ethereum hash.txt --wordlist=/usr/share/wordlists/rockyou.txt

John accepts the same $ethereum$ hash string for both KDF variants and auto-selects the correct code path based on the s or p flag.

A note on scrypt performance

scrypt at n=262144 is intentionally slow. On a modern GPU, throughput for -m 15700 is typically measured in tens of hashes per second rather than millions. A targeted wordlist with password-mutation rules is far more practical than a brute-force approach for real assessments. PBKDF2 at the same iteration count is considerably faster on GPU hardware — expect thousands of candidates per second — but is still not trivially brute-forceable.

What Hash Extractor does

Hash Extractor compiles this extraction logic to WebAssembly and runs it entirely inside the browser. The keystore file is read from local disk via the File API, parsed in WASM, and the resulting $ethereum$ hash string is copied to the clipboard or downloaded. The keystore bytes never leave the machine — there is no server, no upload, and no network request. This mirrors what ethereum2john does on the command line, but with no Python dependency and no file transfer.

The extractor handles both "crypto" and "Crypto" key names, validates that all required hex fields are present, and returns a structured error if the KDF is anything other than scrypt or pbkdf2 — keeping the output reliable for automated pipelines.

Related articles

Why 7z is the trickiest archive to crack: LZMA-compressed headers, AES coder property encoding, and the full $7z$ hash format explained.
Inside the adb backup text header: how PBKDF2 salts, round counts, and a master-key blob become a crackable $ab$ hash.
A technical walkthrough of how Hash Extractor parses Ansible Vault files to produce a crackable $ansible$ hash for hashcat and John the Ripper.