Skip to main content

Nethereum.KeyStore

NuGet: Nethereum.KeyStore | Source: src/Nethereum.KeyStore/

Nethereum.KeyStore

Password-encrypted private key storage using the Web3 Secret Storage Definition standard.

Overview​

Nethereum.KeyStore implements the Web3 Secret Storage Definition for encrypting and storing Ethereum private keys. This is a standard format for encrypted key storage used across the Ethereum ecosystem.

Key Features:

  • AES-128-CTR encryption with password-derived keys
  • Scrypt KDF (memory-hard, ASIC-resistant)
  • PBKDF2 KDF (legacy, faster but less secure)
  • Configurable KDF parameters for performance tuning
  • JSON serialization/deserialization

Use Cases:

  • Encrypted local key storage
  • Wallet file generation
  • Key import/export between applications
  • Performance-tuned encryption for constrained environments (WASM, mobile)

Installation​

dotnet add package Nethereum.KeyStore

Dependencies​

Nethereum:

  • Nethereum.Hex - Hex encoding/decoding

External:

  • BouncyCastle.Cryptography or Portable.BouncyCastle (conditional) - Cryptographic operations

Quick Start​

using Nethereum.KeyStore;
using Nethereum.Signer;
using Nethereum.Hex.HexConvertors.Extensions;

// Generate a new key
var ecKey = EthECKey.GenerateKey();

// Encrypt and generate keystore JSON
var service = new KeyStoreScryptService();
string password = "testPassword";
string json = service.EncryptAndGenerateKeyStoreAsJson(
password,
ecKey.GetPrivateKeyAsBytes(),
ecKey.GetPublicAddress()
);

// Decrypt later
byte[] privateKey = service.DecryptKeyStoreFromJson(password, json);

Usage Examples​

Example 1: Generate Key and Create Keystore (Scrypt)​

using Nethereum.KeyStore;
using Nethereum.Signer;
using Nethereum.Hex.HexConvertors.Extensions;

var ecKey = EthECKey.GenerateKey();
var keyStoreScryptService = new KeyStoreScryptService();
string password = "testPassword";

// Encrypt and serialize to JSON
string json = keyStoreScryptService.EncryptAndGenerateKeyStoreAsJson(
password,
ecKey.GetPrivateKeyAsBytes(),
ecKey.GetPublicAddress()
);

// Save to file
File.WriteAllText($"keystore-{ecKey.GetPublicAddress()}.json", json);

// Decrypt to verify
byte[] key = keyStoreScryptService.DecryptKeyStoreFromJson(password, json);
Assert.Equal(ecKey.GetPrivateKey(), key.ToHex(true));

Example 2: Custom Scrypt Parameters (Performance Tuning)​

using Nethereum.KeyStore;
using Nethereum.KeyStore.Model;
using Nethereum.Signer;
using Nethereum.Hex.HexConvertors.Extensions;

var keyStoreService = new KeyStoreScryptService();

// Lower N for faster encryption (WASM, mobile, testing)
// Default: N=262144, R=1, P=8, Dklen=32
var scryptParams = new ScryptParams { Dklen = 32, N = 32, R = 1, P = 8 };

var ecKey = EthECKey.GenerateKey();
string password = "testPassword";

// Encrypt with custom parameters — pass address and ScryptParams object
var keyStore = keyStoreService.EncryptAndGenerateKeyStore(
password,
ecKey.GetPrivateKeyAsBytes(),
ecKey.GetPublicAddress(),
scryptParams
);

// Or use the JSON shortcut directly
string json = keyStoreService.EncryptAndGenerateKeyStoreAsJson(
password, ecKey.GetPrivateKeyAsBytes(), ecKey.GetPublicAddress(), scryptParams);

// Decrypt
byte[] decryptedKey = keyStoreService.DecryptKeyStoreFromJson(password, json);

Example 3: Decrypt Existing Keystore (Scrypt)​

using Nethereum.KeyStore;
using Nethereum.Hex.HexConvertors.Extensions;

var scryptKeyStoreJson = @"{
""crypto"" : {
""cipher"" : ""aes-128-ctr"",
""cipherparams"" : {
""iv"" : ""83dbcc02d8ccb40e466191a123791e0e""
},
""ciphertext"" : ""d172bf743a674da9cdad04534d56926ef8358534d458fffccd4e6ad2fbde479c"",
""kdf"" : ""scrypt"",
""kdfparams"" : {
""dklen"" : 32,
""n"" : 262144,
""r"" : 1,
""p"" : 8,
""salt"" : ""ab0c7876052600dd703518d6fc3fe8984592145b591fc8fb5c6d43190334ba19""
},
""mac"" : ""2103ac29920d71da29f15d75b4a16dbe95cfd7ff8faea1056c33131d846e3097""
},
""id"" : ""3198bc9c-6672-5ab3-d995-4942343ae5b6"",
""version"" : 3
}";

string password = "testpassword";
var keyStoreScryptService = new KeyStoreScryptService();

// Deserialize and decrypt
var keyStore = keyStoreScryptService.DeserializeKeyStoreFromJson(scryptKeyStoreJson);
byte[] privateKey = keyStoreScryptService.DecryptKeyStore(password, keyStore);

Console.WriteLine($"Private Key: {privateKey.ToHex()}");
// Output: 7a28b5ba57c53603b0b07b56bba752f7784bf506fa95edc395f5cf6c7514fe9d

Example 4: PBKDF2 Keystore (Legacy)​

using Nethereum.KeyStore;
using Nethereum.Signer;
using Nethereum.Hex.HexConvertors.Extensions;

var ecKey = EthECKey.GenerateKey();
var keyStorePbkdf2Service = new KeyStorePbkdf2Service();
string password = "testPassword";

// Encrypt with PBKDF2 (faster but less secure than Scrypt)
string json = keyStorePbkdf2Service.EncryptAndGenerateKeyStoreAsJson(
password,
ecKey.GetPrivateKeyAsBytes(),
ecKey.GetPublicAddress()
);

// Decrypt
byte[] key = keyStorePbkdf2Service.DecryptKeyStoreFromJson(password, json);
Assert.Equal(ecKey.GetPrivateKey(), key.ToHex(true));

Example 5: Detect KDF Type​

using Nethereum.KeyStore;

string keystoreJson = File.ReadAllText("wallet.json");
var keyStoreKdfChecker = new KeyStoreKdfChecker();

var kdfType = keyStoreKdfChecker.GetKeyStoreKdfType(keystoreJson);

if (kdfType == KeyStoreKdfChecker.KdfType.scrypt)
{
var service = new KeyStoreScryptService();
byte[] privateKey = service.DecryptKeyStoreFromJson(password, keystoreJson);
}
else if (kdfType == KeyStoreKdfChecker.KdfType.pbkdf2)
{
var service = new KeyStorePbkdf2Service();
byte[] privateKey = service.DecryptKeyStoreFromJson(password, keystoreJson);
}

Example 6: Default Keystore Service​

using Nethereum.KeyStore;
using Nethereum.Signer;
using Nethereum.Hex.HexConvertors.Extensions;

var ecKey = EthECKey.GenerateKey();
var keyStoreService = new KeyStoreService();
string password = "testPassword";

// Uses default Scrypt parameters
string json = keyStoreService.EncryptAndGenerateDefaultKeyStoreAsJson(
password,
ecKey.GetPrivateKeyAsBytes(),
ecKey.GetPublicAddress()
);

byte[] key = keyStoreService.DecryptKeyStoreFromJson(password, json);
Assert.Equal(ecKey.GetPrivateKey(), key.ToHex(true));

API Reference​

KeyStoreServiceBase<T>​

KeyStoreScryptService and KeyStorePbkdf2Service are thin subclasses of this abstract base (T : KdfParams) — almost every method below is inherited, not redeclared per-KDF. Note the EncryptAndGenerateKeyStoreAsJson overloads take the address parameter as addresss (a real typo in the shipped signature, kept for source compatibility); the object-returning EncryptAndGenerateKeyStore overloads spell it correctly as address.

public abstract class KeyStoreServiceBase<T> : IKeyStoreService<T> where T : KdfParams
{
public const int CurrentVersion = 3;

// Key store (private key) encryption
public KeyStore<T> EncryptAndGenerateKeyStore(string password, byte[] privateKey, string address);
public KeyStore<T> EncryptAndGenerateKeyStore(string password, byte[] privateKey, string address, T kdfParams);
public string EncryptAndGenerateKeyStoreAsJson(string password, byte[] privateKey, string addresss);
public string EncryptAndGenerateKeyStoreAsJson(string password, byte[] privateKey, string addresss, T kdfParams);

public KeyStoreKdfLimits KdfLimits { get; set; }

public byte[] DecryptKeyStoreFromJson(string password, string json);
public byte[] DecryptKeyStoreFromJson(string password, string json, KeyStoreKdfLimits limits);
public virtual byte[] DecryptKeyStore(string password, KeyStore<T> keyStore);
public byte[] DecryptKeyStore(string password, KeyStore<T> keyStore, KeyStoreKdfLimits limits);

public abstract KeyStore<T> DeserializeKeyStoreFromJson(string json); // overridden per-KDF
public abstract string SerializeKeyStoreToJson(KeyStore<T> keyStore); // overridden per-KDF
public abstract string GetKdfType(); // "scrypt" / "pbkdf2"
public virtual string GetCipherType(); // "aes-128-ctr"

// Generic payload (arbitrary byte[]/string) encryption - same KDF/cipher, no "address" field
public CryptoStore<T> EncryptAndGenerateCryptoStore(string password, byte[] payload);
public CryptoStore<T> EncryptAndGenerateCryptoStore(string password, byte[] payload, T kdfParams);
public string EncryptAndGenerateCryptoStoreAsJson(string password, byte[] payload);
public string EncryptAndGenerateCryptoStoreFromStringAsJson(string password, string payload);
public byte[] DecryptCryptoStoreFromJson(string password, string json);
public byte[] DecryptCryptoStore(string password, CryptoStore<T> cryptoStore);
}

KeyStoreScryptService : KeyStoreServiceBase<ScryptParams>​

Scrypt-based keystore encryption (recommended). Adds only the KDF-specific pieces; everything else above is inherited unchanged.

public class KeyStoreScryptService : KeyStoreServiceBase<ScryptParams>
{
public const string KdfType = "scrypt";
}

Default Scrypt Parameters (GetDefaultParams()):

N = 262144  // CPU/memory cost (2^18)
R = 1 // Block size
P = 8 // Parallelization
Dklen = 32 // Derived key length

KeyStorePbkdf2Service : KeyStoreServiceBase<Pbkdf2Params>​

PBKDF2-based keystore encryption (legacy). Same relationship to the base class as KeyStoreScryptService.

public class KeyStorePbkdf2Service : KeyStoreServiceBase<Pbkdf2Params>
{
public const string KdfType = "pbkdf2";
}

Default PBKDF2 Parameters (GetDefaultParams()):

Count = 262144      // Iteration count
Prf = "hmac-sha256"
Dklen = 32 // Derived key length

KeyStoreService​

Unified service: wraps a KeyStoreScryptService + KeyStorePbkdf2Service + KeyStoreKdfChecker internally, and also carries the file-naming/address-extraction and raw-payload-encryption helpers that are NOT on KeyStoreServiceBase<T>.

public class KeyStoreService
{
public KeyStoreService();
public KeyStoreService(KeyStoreKdfChecker keyStoreKdfChecker, KeyStoreScryptService keyStoreScryptService,
KeyStorePbkdf2Service keyStorePbkdf2Service);

// Encrypt with default Scrypt parameters
public string EncryptAndGenerateDefaultKeyStoreAsJson(string password, byte[] key, string address);

// Cost caps applied to every decryption (shared by the scrypt and pbkdf2 services)
public KeyStoreKdfLimits KdfLimits { get; set; }

// Decrypt (auto-detects KDF type via KeyStoreKdfChecker)
public byte[] DecryptKeyStoreFromJson(string password, string json);
public byte[] DecryptKeyStoreFromJson(string password, string json, KeyStoreKdfLimits limits);
#if !PCL
public byte[] DecryptKeyStoreFromFile(string password, string filePath);
#endif

// Read the "address" field straight out of a keystore JSON document, without deserializing it fully
public string GetAddressFromKeyStore(string json);

// "UTC--<ISO8601 with ':' replaced by '-'>--<address without 0x>", the standard Ethereum
// keystore file-naming convention
public string GenerateUTCFileName(string address);

// Generic payload encryption (delegates to the internal KeyStoreScryptService's
// CryptoStore<ScryptParams> methods - no address field, just password + payload)
public string EncryptPayloadAsJson(string password, byte[] data);
public string EncryptPayloadFromStringAsJson(string password, string data);
public byte[] DecryptPayloadFromJson(string password, string json);
public string DecryptPayloadToUtf8String(string password, string json);
}

KeyStoreKdfLimits​

The address check on decryption is a tamper heuristic: it only applies when the keystore has a well-formed address field (the v3 MAC does not cover the IV, and a keystore without an address is valid).

Decryption validates every KDF parameter before allocating and rejects a keystore outside these limits with ArgumentException (scrypt: N a power of two above 1, r and p at least 1, dklen 32; pbkdf2: c at least 1, dklen 32, prf hmac-sha256; cipher aes-128-ctr; version 3). The cost caps default to the values below and are set on KeyStoreService.KdfLimits or KeyStoreServiceBase<T>.KdfLimits, or passed to KeyStoreService.DecryptKeyStoreFromJson(password, json, limits).

public class KeyStoreKdfLimits
{
public const long DefaultMaxScryptMemoryBytes = 512L * 1024 * 1024;
public const int DefaultMaxScryptP = 16;
public const int DefaultMaxPbkdf2Iterations = 10000000;

public static KeyStoreKdfLimits Default { get; }

public long MaxScryptMemoryBytes { get; set; } = DefaultMaxScryptMemoryBytes; // 128 * r * N
public int MaxScryptP { get; set; } = DefaultMaxScryptP;
public int MaxPbkdf2Iterations { get; set; } = DefaultMaxPbkdf2Iterations;
}
var service = new KeyStoreService
{
KdfLimits = new KeyStoreKdfLimits { MaxScryptMemoryBytes = 128L * 4096 - 1 }
};

Assert.Throws<ArgumentException>(() => service.DecryptKeyStoreFromJson(password, json));

var raised = new KeyStoreKdfLimits { MaxScryptMemoryBytes = 128L * 4096 };
var privateKey = service.DecryptKeyStoreFromJson(password, json, raised);

Backed by FoundationsKeyStoreDocExampleTests.Given_AKeyStoreCostingMoreThanTheConfiguredLimit_When_Decrypted_Then_ItIsRefusedUntilTheLimitIsRaised. Hostile fields (an unsupported cipher, a field that is not hex, a wrong-length IV) also throw ArgumentException, and a keystore whose well-formed address is not the address of the decrypted key throws DecryptionException; the MAC is compared in constant time.

Assert.Throws<ArgumentException>(() => new KeyStoreService().DecryptKeyStoreFromJson(password, wrongCipherJson));
Assert.Throws<DecryptionException>(() => new KeyStoreService().DecryptKeyStoreFromJson(password, jsonWithAnotherAddress));

Backed by FoundationsKeyStoreDocExampleTests.Given_AKeyStoreWhoseAddressIsNotTheAddressOfItsKey_When_Decrypted_Then_ADecryptionExceptionIsThrown and FoundationsKeyStoreDocExampleTests.Given_AKeyStoreWithAnUnsupportedCipherOrNonHexField_When_Decrypted_Then_ArgumentExceptionIsThrown.

KeyStoreKdfChecker​

Detect KDF type from JSON. This is the only detection API — there is no IsScryptKdf/IsPbkdf2Kdf.

public class KeyStoreKdfChecker
{
public enum KdfType { scrypt, pbkdf2 }

// Throws Exception("Invalid KeyStore json") wrapping an InvalidKdfException if "crypto.kdf" is
// neither "scrypt" nor "pbkdf2"; throws ArgumentException("Invalid KeyStore json") if the
// text is not a keystore document at all
public KdfType GetKeyStoreKdfType(string json);
}

Model Classes​

ScryptParams and Pbkdf2Params both derive from KdfParams, which carries Dklen/Salt; N/R/P and Count/Prf live on the subclasses. Note Pbkdf2Params.Count (JSON "c"), not C.

public class KdfParams
{
[JsonProperty("dklen")]
public int Dklen { get; set; } // Derived key length (32)

[JsonProperty("salt")]
public string Salt { get; set; } // Random salt (hex)
}

public class ScryptParams : KdfParams
{
[JsonProperty("n")]
public int N { get; set; } // CPU/memory cost (262144)

[JsonProperty("r")]
public int R { get; set; } // Block size (1)

[JsonProperty("p")]
public int P { get; set; } // Parallelization (8)
}

public class Pbkdf2Params : KdfParams
{
[JsonProperty("c")]
public int Count { get; set; } // Iteration count (262144)

[JsonProperty("prf")]
public string Prf { get; set; } // PRF algorithm (hmac-sha256)
}

public class KeyStore<TKdfParams> where TKdfParams : KdfParams
{
public CryptoInfo<TKdfParams> Crypto { get; set; }
public string Id { get; set; } // UUID
public string Address { get; set; } // Ethereum address (optional)
public int Version { get; set; } // Always 3
}

Scrypt Parameter Tuning​

Default Parameters (Desktop/Server)​

N = 262144  // 2^18 - Strong security, ~100ms encryption
R = 1
P = 8

Use for: Desktop applications, servers, production wallets

Low-Cost Parameters (WASM/Mobile/Testing)​

N = 32      // 2^5 - Fast encryption, weaker security
R = 1
P = 8

Use for: Browser WASM, mobile apps, development/testing

High-Security Parameters​

N = 1048576  // 2^20 - Very strong security, ~3s encryption
R = 8
P = 1

Use for: Cold storage, high-value accounts, paranoid security

This cost (about 1 GiB) is above the default decryption limit of 512 MiB; decrypting it needs a raised KeyStoreKdfLimits.MaxScryptMemoryBytes.

Parameter Effects​

ParameterEffectSecurity ImpactPerformance Impact
NCPU/memory costExponentialExponential
RBlock sizeLinearLinear
PParallelizationLinearLinear (if parallel)

N dominates: Doubling N doubles time and memory. Scrypt needs about 128 x N x R bytes, so the default N=262144 with R=1 uses ~32MB RAM.

Web3 Secret Storage Format​

Keystore JSON structure:

{
"crypto": {
"cipher": "aes-128-ctr",
"cipherparams": { "iv": "..." },
"ciphertext": "...",
"kdf": "scrypt",
"kdfparams": {
"dklen": 32,
"n": 262144,
"r": 1,
"p": 8,
"salt": "..."
},
"mac": "..."
},
"id": "3198bc9c-6672-5ab3-d995-4942343ae5b6",
"version": 3
}

Fields:

  • cipher: Always aes-128-ctr
  • ciphertext: Encrypted private key
  • kdf: scrypt or pbkdf2
  • kdfparams: KDF configuration
  • mac: HMAC for integrity verification
  • version: Always 3

Important Notes​

Scrypt vs PBKDF2​

FeatureScryptPBKDF2
SecurityMemory-hard, ASIC-resistantCPU-only, ASIC-vulnerable
SpeedSlower (~100ms default)Faster (~50ms)
RecommendationUse thisLegacy only

Use Scrypt unless you need compatibility with very old systems.

File Naming Convention​

Standard naming convention used across Ethereum tools:

UTC--<created_at UTC ISO8601>--<address hex>

Example:

UTC--2024-01-15T10-30-45.123Z--0x12890d2cce102216644c59dae5baed380d84830c

Security Considerations​

  1. Password strength is critical - No KDF can protect weak passwords
  2. N parameter tradeoff - Higher N = more secure but slower
  3. Salt is auto-generated - Uses cryptographically secure random bytes
  4. MAC prevents tampering - Detects modified ciphertext
  5. AES-128-CTR - Standard encryption mode, secure when properly implemented

Used By​

  • Nethereum.Accounts - Account management with keystore loading

Dependencies​

  • Nethereum.Hex - Hex encoding/decoding

Additional Resources​