# How NEP-6 Wallet Files Store Account Keys and Encryption Methods

> Discover how NEP-6 wallet files store account keys using NEP-2 encryption AES-256-ECB and scrypt. Understand the encryption parameters within the JSON structure.

- Repository: [The Neo Project/neo](https://github.com/neo-project/neo)
- Tags: how-to-guide
- Published: 2026-03-08

---

**NEP-6 wallet files store account keys as NEP-2 encrypted strings using scrypt key derivation and AES-256-ECB encryption, with parameters defined in the wallet's JSON structure.**

NEP-6 is the standard wallet format used by the Neo blockchain to persist user accounts in human-readable JSON files. According to the neo-project/neo source code, these NEP-6 wallet files implement a specific encryption pipeline that protects private keys while allowing watch-only accounts to exist without sensitive data.

## NEP-6 Wallet File Structure and Key Storage

### JSON Schema and Account Array

NEP-6 wallet files are simple JSON documents that contain a top-level `accounts` array. Each object in this array represents a single Neo account with properties for the address, label, and encryption status. The wallet also stores global `scrypt` parameters that control the key derivation function used across all encrypted accounts in the file.

### The Key Property and NEP-2 Strings

Each account entry contains a `key` property that serves as the storage mechanism for private key data:

- **`null`** — Indicates a watch-only account with no private key stored
- **NEP-2 string** — A Base58Check-encoded encrypted private key when the account is password-protected

In [`src/Neo/Wallets/NEP6/NEP6Account.cs`](https://github.com/neo-project/neo/blob/main/src/Neo/Wallets/NEP6/NEP6Account.cs), the `NEP6Account` class stores this string in the private field `nep2key` and only decrypts it on demand when `GetKey()` or `GetKey(string password)` is called. The resulting `KeyPair` is cached after a successful password check to avoid repeated decryption operations.

## NEP-2 Encryption Algorithm and Parameters

### Scrypt Key Derivation

When encrypting or decrypting a private key, the wallet uses the **NEP-2** algorithm with scrypt-based key derivation. The process begins with `Scrypt.Generate(passphrase, addresshash, N, r, p, 64)` to derive a 64-byte key from the user's password and the account's address hash.

The wallet's `ScryptParameters` define the computational cost factors stored in the JSON file's `scrypt` section. Default values used by the Neo implementation are:
- **N**: 16384 (iteration count)
- **r**: 8 (block size)
- **p**: 8 (parallelization factor)

These parameters are passed to the decryption routine in [`src/Neo/Wallets/Wallet.cs`](https://github.com/neo-project/neo/blob/main/src/Neo/Wallets/Wallet.cs) via the `GetPrivateKeyFromNEP2` method.

### AES-256-ECB Encryption and XOR Operations

The NEP-2 decryption pipeline implemented in `Wallet.GetPrivateKeyFromNEP2` follows this sequence:

1. **Split the derived key**: The 64-byte scrypt output divides into `derivedhalf1` (first 32 bytes) and `derivedhalf2` (last 32 bytes)
2. **AES decryption**: The encrypted private key (32 bytes) decrypts using **AES-256-ECB** with `derivedhalf2` as the encryption key
3. **XOR operation**: The decrypted result is XOR-ed with `derivedhalf1` to obtain the clear private key

The same routine handles encryption when creating new accounts, performing the inverse operations to generate the NEP-2 string stored in the wallet file.

## Implementation in the Neo Source Code

### NEP6Account.cs and Lazy Decryption

The `NEP6Account` class in [`src/Neo/Wallets/NEP6/NEP6Account.cs`](https://github.com/neo-project/neo/blob/main/src/Neo/Wallets/NEP6/NEP6Account.cs) encapsulates the lazy decryption pattern. It maintains the encrypted `nep2key` string internally and only materializes the `KeyPair` when explicitly requested through `GetKey()` methods. This design ensures that private keys remain encrypted in memory until the moment they are needed for signing operations.

### Wallet.cs and GetPrivateKeyFromNEP2

The core cryptographic operations reside in [`src/Neo/Wallets/Wallet.cs`](https://github.com/neo-project/neo/blob/main/src/Neo/Wallets/Wallet.cs). The `GetPrivateKeyFromNEP2` method implements both overloads of the NEP-2 decryption algorithm, handling the scrypt key derivation, AES-256-ECB decryption, and XOR operations required to recover the private key from the encrypted string.

### NEP6Wallet.cs and Password Verification

The `NEP6Wallet` class in [`src/Neo/Wallets/NEP6/NEP6Wallet.cs`](https://github.com/neo-project/neo/blob/main/src/Neo/Wallets/NEP6/NEP6Wallet.cs) manages the wallet file's JSON structure and password validation. The `VerifyPasswordInternal` method decrypts the first available encrypted account to validate that the provided password can successfully unlock the stored keys, using the scrypt parameters defined in the wallet's configuration.

## Practical Code Examples

### Load an Existing NEP-6 Wallet and Retrieve a Decrypted Private Key

```csharp
using Neo.Wallets.NEP6;
using Neo;

// Open the wallet (read-only if password is null)
var wallet = NEP6Wallet.Open("mywallet.json", "myPassword", ProtocolSettings.Default);

// Find the first account that has a key
var account = wallet.GetAccounts().FirstOrDefault(a => a.HasKey);
if (account != null)
{
    // Decrypt the key (NEP-2 → KeyPair)
    KeyPair key = account.GetKey()!;          // uses the wallet's stored password
    Console.WriteLine($"Public key: {key.PublicKey}");
}

```

`GetKey()` internally calls `wallet.DecryptKey(nep2key)` which uses `Wallet.GetPrivateKeyFromNEP2`.

### Add a New Password-Protected Account

```csharp
using Neo.Wallets.NEP6;
using Neo;

// Create a new wallet (will generate a fresh scrypt config)
var wallet = new NEP6Wallet("newwallet.json", "strongPass", ProtocolSettings.Default);

// Generate a fresh key pair
byte[] privateKey = new byte[32];
RandomNumberGenerator.Fill(privateKey);
var keyPair = new KeyPair(privateKey);

// Create the account – the constructor encrypts the private key to NEP-2
var account = wallet.CreateAccount(privateKey);
wallet.Save();   // persists the JSON with the encrypted "key" field

```

### Change Wallet Password (Re-Encrypts All NEP-2 Strings)

```csharp
wallet.ChangePassword("oldPass", "newPass");
wallet.Save();   // writes the newly encrypted keys

```

`ChangePassword` calls `NEP6Account.ChangePasswordPrepare` for each account, which re-exports the private key with the new password using `KeyPair.Export` (internally NEP-2).

## Summary

- **NEP-6 wallet files** are JSON documents that store account metadata and encrypted private keys in an `accounts` array.
- **Account keys** are stored as **NEP-2 encrypted strings** in the `key` property, or `null` for watch-only accounts.
- **Encryption** uses **scrypt** key derivation (default: N=16384, r=8, p=8) followed by **AES-256-ECB** decryption and XOR operations.
- **Implementation** resides in [`NEP6Account.cs`](https://github.com/neo-project/neo/blob/main/NEP6Account.cs) for lazy decryption, [`Wallet.cs`](https://github.com/neo-project/neo/blob/main/Wallet.cs) for cryptographic operations, and [`NEP6Wallet.cs`](https://github.com/neo-project/neo/blob/main/NEP6Wallet.cs) for file management.
- **Password changes** trigger re-encryption of all account keys using the new passphrase while maintaining the same NEP-2 format.

## Frequently Asked Questions

### What is the difference between NEP-6 and NEP-2 in Neo wallets?

NEP-2 defines the encryption standard for individual private keys, specifying how a single key is encrypted using scrypt and AES-256-ECB. NEP-6 defines the wallet file format—a JSON structure that can hold multiple accounts, each potentially containing a NEP-2 encrypted key string, along with metadata like labels, contracts, and scrypt parameters.

### How does the wallet verify that a password is correct without decrypting all accounts?

The `NEP6Wallet.VerifyPasswordInternal` method decrypts only the first available encrypted account (or any single account with a key) using the provided password. If the scrypt-derived key successfully decrypts the NEP-2 string and produces a valid private key matching the account's public address, the password is validated without needing to process every account in the wallet.

### Can I import a NEP-6 wallet without knowing the password?

Yes, but only in read-only mode. If you open a `NEP6Wallet` with a `null` password, you can view account addresses, labels, and balances, but the `key` field remains encrypted and the `GetKey()` method will return null. You cannot sign transactions or access the private key without the correct password to decrypt the NEP-2 strings.

### What happens when I change the password on a NEP-6 wallet?

When you call `wallet.ChangePassword(oldPass, newPass)`, the wallet iterates through all accounts and calls `NEP6Account.ChangePasswordPrepare`, which decrypts each private key using the old password and re-encrypts it using the new password via `KeyPair.Export`. The wallet then saves the updated JSON file with all accounts containing newly encrypted NEP-2 strings derived from the new passphrase, while the underlying scrypt parameters remain unchanged.