Ethereum Web Wallet Development with ethers.js – Keystore Import and Export

·

Developing a decentralized Ethereum web wallet from scratch requires a solid understanding of secure key management. This article, the second in a four-part series, focuses on wallet account import and export using Keystore files, diving into the underlying principles of encrypted key storage and demonstrating practical implementation using ethers.js.

Whether you're building your own wallet interface or integrating blockchain login functionality, mastering Keystore handling is essential for security and usability.


Importing Accounts Created with Geth

In the previous article, we explored creating Ethereum accounts using private keys and mnemonic phrases—processes that also serve as methods for importing existing accounts.

A common question among developers is: How can I import an account generated by Geth? If you've used Geth before, you’re likely familiar with its automatic generation of a Keystore JSON file when creating an account. This file stores your encrypted private key and is typically located in the keystore directory within your Ethereum data folder (e.g., ~/.ethereum/keystore).

While ethers.js simplifies Keystore import with just one function call, understanding how these files work under the hood significantly improves your ability to build secure applications.

👉 Discover how to securely manage Ethereum wallets using modern tools


Understanding the Ethereum Keystore File

Why Use a Keystore File?

As discussed in our guide on HD wallets and BIP39, a private key essentially is an Ethereum account. The most basic way to store it is in plain text—but this poses massive security risks. If your device is compromised, so are your funds.

The Keystore file solves this problem by storing the private key in an encrypted format. When you want to send a transaction, the wallet decrypts the key using a password. This means attackers must obtain both the Keystore file and the correct password to access your assets—dramatically improving security.


How Is a Keystore File Generated?

Ethereum uses symmetric encryption (specifically aes-128-ctr) to protect the private key. But first, it needs a strong encryption key derived from your password. This is where Key Derivation Functions (KDF) come in.

Key Derivation with Scrypt

A KDF generates cryptographic keys from passwords. While PBKDF2 is used in BIP39 mnemonics, Ethereum’s Keystore standard uses Scrypt, a memory-hard function designed to resist brute-force attacks.

The formula for key derivation is:

DK = Scrypt(salt, dk_len, n, r, p)

Where:

For example, common settings include "n": 262144, "r": 8, "p": 1—making each attempt at guessing the password computationally expensive.

This derived key (DK) is then used in the next step: encrypting the actual private key.


Symmetric Encryption of the Private Key

Once the encryption key is generated via Scrypt, the wallet uses AES-128-CTR mode to encrypt the private key. This method requires an initialization vector (IV), which ensures identical plaintexts produce different ciphertexts each time.

The output of this process is stored in the ciphertext field of the Keystore JSON.


Anatomy of a Keystore JSON File

Here’s an example of what a real Keystore file looks like:

{
  "address": "856e604698f79cef417aab...",
  "crypto": {
    "cipher": "aes-128-ctr",
    "ciphertext": "13a3ad2135bef1ff228e399dfc8d7757eb4bb1a81d1b31....",
    "cipherparams": {
      "iv": "92e7468e8625653f85322fb3c..."
    },
    "kdf": "scrypt",
    "kdfparams": {
      "dklen": 32,
      "n": 262144,
      "p": 1,
      "r": 8,
      "salt": "3ca198ce53513ce01bd651aee54b16b6a...."
    },
    "mac": "10423d837830594c18a91097d09b7f2316..."
  },
  "id": "5346bac5-0a6f-4ac6-baba-e2f3ad464f3f",
  "version": 3
}

Let’s break down the key fields:

👉 Learn how to integrate secure wallet features into your dApp


How Does Password Verification Work?

You might wonder: Can’t any password decrypt something? How do we know it’s correct?

That’s where the mac field comes in. It’s calculated as:

mac = keccak256(DK[16:32] + ciphertext)

Only the correct password will produce the right derived key (DK), leading to a matching MAC value. If the computed MAC doesn't match the one in the file, the password is rejected.

This prevents users from accidentally using wrong passwords and helps block automated cracking attempts.


Implementing Keystore Import and Export with ethers.js

Thanks to ethers.js, working with Keystore files is straightforward. The library provides built-in methods for both encryption and decryption.

Core Methods

// Import from encrypted JSON (Keystore)
ethers.Wallet.fromEncryptedJson(json, password)
  .then(wallet => {
    console.log("Wallet loaded:", wallet.address);
  })
  .catch(error => {
    console.error("Invalid password or JSON");
  });

// Export wallet to encrypted JSON
wallet.encrypt(password)
  .then(json => {
    // Save or display JSON
  });

These asynchronous functions handle all cryptographic operations securely behind the scenes.


Exporting a Wallet to Keystore

Here’s a simple HTML interface for exporting a wallet:

<h3>Keystore Export:</h3>
<table>
  <tr>
    <th>Password:</th>
    <td><input type="password" placeholder="(password)" id="save-keystore-file-pwd" /></td>
  </tr>
  <tr>
    <td></td>
    <td><div id="save-keystore" class="submit">Export</div></td>
  </tr>
</table>

JavaScript logic:

$('#save-keystore').click(function() {
  const pwd = $('#save-keystore-file-pwd').val();
  wallet.encrypt(pwd).then(json => {
    const blob = new Blob([json], { type: 'application/json' });
    saveAs(blob, 'keystore.json'); // Using FileSaver.js
  });
});

This allows users to securely download their encrypted wallet for backup.


Importing a Keystore File

For importing, we need a file input and password field:

<h2>Load Keystore File</h2>
<table>
  <tr>
    <th>Keystore:</th>
    <td>
      <div class="file" id="select-wallet-drop">Drag JSON here</div>
      <input type="file" id="select-wallet-file" />
    </td>
  </tr>
  <tr>
    <th>Password:</th>
    <td><input type="password" placeholder="(password)" id="select-wallet-password" /></td>
  </tr>
  <tr>
    <td></td>
    <td><div id="select-submit-wallet" class="submit disable">Decrypt</div></td>
  </tr>
</table>

File reading and decryption:

const fileReader = new FileReader();
fileReader.onload = function(e) {
  const json = e.target.result;
  const password = $('#select-wallet-password').val();

  ethers.Wallet.fromEncryptedJson(json, password)
    .then(wallet => {
      console.log("Imported wallet:", wallet.address);
      // Proceed with app logic
    })
    .catch(err => {
      alert("Invalid password or corrupted file");
    });
};

fileReader.readAsText(inputFile.files[0]);

This enables seamless account recovery across devices.


Frequently Asked Questions

Q: What happens if I lose my Keystore password?
A: Unfortunately, there’s no way to recover the private key without the correct password. Always store your password securely—preferably using a hardware wallet or trusted password manager.

Q: Is it safe to store Keystore files in cloud storage?
A: Yes, only if they are properly encrypted and you trust your password's strength. However, avoid storing them alongside the password. Use separate secure channels for each.

Q: Can I use the same Keystore file on multiple devices?
A: Absolutely. Once exported, a Keystore file can be imported on any compatible Ethereum wallet application across devices.

Q: How does Scrypt compare to other KDFs like PBKDF2?
A: Scrypt is more resistant to hardware-based attacks because it consumes significant memory during computation, making large-scale brute-force attempts impractical compared to PBKDF2.

Q: Are V3 Keystores still secure today?
A: Yes, provided strong passwords and default parameters (like high n values) are used. They remain widely supported across wallets and tools.

Q: Can ethers.js work with non-Ethereum EVM chains?
A: Yes! Since most EVM-compatible networks (like Polygon, BSC, Arbitrum) use the same account model, ethers.js works seamlessly across them when configured correctly.


👉 Start building secure, cross-chain wallet integrations today