Blog | G5 Cyber Security

Client-Side Private Key Storage

TL;DR

Storing private keys on a client is inherently risky. The best approach balances security with usability, typically involving encrypted storage within the user’s browser or operating system. Avoid storing keys in plain text at all costs. This guide covers several methods, from basic to more advanced, outlining their pros and cons.

1. Understand the Risks

Before diving into solutions, acknowledge the dangers:

No solution is foolproof. Mitigation focuses on reducing these risks.

2. Basic Approach: Browser Local Storage (Discouraged)

While simple, storing encrypted keys in browser local storage is not recommended for sensitive applications due to security limitations. It’s vulnerable to XSS attacks and can be accessed by malicious extensions.

// Example - DO NOT USE IN PRODUCTION!  This is illustrative only.
const encryptionKey = 'your-secret-encryption-key'; // Replace with a strong key
const encryptedPrivateKey = encrypt('my private key', encryptionKey);
localStorage.setItem('privateKey', encryptedPrivateKey);

Why it’s bad: The encryptionKey is in your JavaScript code, making it easily discoverable.

3. Better Approach: Web Crypto API & Browser Key Storage

The Web Crypto API offers more secure encryption options using the browser’s built-in cryptographic functions. This allows you to encrypt the private key before storing it in local storage, and ideally use a password derived key.

  1. Generate a Random Encryption Key: Use crypto.getRandomValues() to create a strong, random encryption key.
  2. Encrypt the Private Key: Employ algorithms like AES-GCM for symmetric encryption.
  3. Store Encrypted Data: Save the encrypted private key in browser local storage.
  4. Password Protection (Recommended): Derive an encryption key from a user’s password using PBKDF2 or Argon2 before encrypting the private key. This adds another layer of security.
// Example - Simplified for illustration.
async function encryptPrivateKey(privateKey, password) {
  const encoder = new TextEncoder();
  const encodedPassword = encoder.encode(password);
  const keyMaterial = await crypto.subtle.importKey("raw", encodedPassword, { name: "PBKDF2", hash: "SHA-256" }, true, ["encrypt", "decrypt"])
  const iv = crypto.getRandomValues(new Uint8Array(16));
  const encrypted = await crypto.subtle.encrypt({
    name: 'AES-GCM',
    iv: iv,
  }, keyMaterial, encoder.encode(privateKey));
  return { ciphertext: Array.from(new Uint8Array(encrypted)), iv: Array.from(iv) };
}

Important: Handle the encryption key securely. Never store it directly in your code.

4. Advanced Approach: Operating System Key Stores

For higher security, leverage operating system-level key stores (e.g., Windows Credential Manager, macOS Keychain). This moves the private key storage outside of the browser’s control and into a more secure environment.

Benefits:

Drawbacks:

5. Considerations for User Experience

6. Best Practices

Exit mobile version