# How 5ire Encrypts Stored API Keys and Protects Sensitive Data

> Learn how 5ire encrypts stored API keys with AES-256-CBC and protects sensitive data using secure storage methods. Discover critical security details.

- Repository: [Ironben/5ire](https://github.com/nanbingxyz/5ire)
- Tags: security-best-practices
- Published: 2026-03-07

---

**5ire encrypts API keys using AES-256-CBC with a per-installation secret derived from the `CRYPTO_SECRET` environment variable, storing only the encrypted payload in the local Electron store while keeping the encryption key in memory or the OS keychain.**

The open-source Electron application **5ire** (available at `nanbingxyz/5ire`) functions as a local AI assistant that requires users to input sensitive provider credentials. To prevent credential leakage, the codebase implements a multi-layer encryption pipeline that ensures API keys never persist in plaintext on disk.

## Encryption Architecture Overview

5ire’s security model relies on **symmetric encryption** combined with **environment-based secrets**. The architecture ensures that even if an attacker gains read-only access to the user’s configuration directory at `~/.config/5ire`, they cannot recover usable API keys without the per-installation master secret.

The protection layers include:

- **Per-installation master secret** (`CRYPTO_SECRET`) loaded at runtime
- **AES-256-CBC encryption** implemented in [`src/main/services/encryptor.ts`](https://github.com/nanbingxyz/5ire/blob/main/src/main/services/encryptor.ts)
- **Random IV generation** for every encryption operation to prevent pattern analysis
- **IPC isolation** where encryption operations run in the main process, separate from the renderer
- **Optional OS keychain storage** for the master secret itself, as documented in [`docs/ARCHITECTURE.md`](https://github.com/nanbingxyz/5ire/blob/main/docs/ARCHITECTURE.md)

## The Encryption Pipeline

### Loading the Master Secret

During application startup, [`src/main/main.ts`](https://github.com/nanbingxyz/5ire/blob/main/src/main/main.ts) (line 87) reads the `CRYPTO_SECRET` environment variable and injects it into the `Environment` class as `cryptoSecret`. This value serves as the salt for all subsequent encryption operations and never gets written to the store itself.

```typescript
// Conceptual flow from main.ts
const cryptoSecret = process.env.CRYPTO_SECRET || '';
Environment.cryptoSecret = cryptoSecret;

```

### Key Derivation Logic

Before encrypting any data, the `Encryptor` class in [`src/main/services/encryptor.ts`](https://github.com/nanbingxyz/5ire/blob/main/src/main/services/encryptor.ts) (lines 21-23) derives a unique 32-byte key using the **SHA-256** hash of the concatenated master secret and a user-provided context string (such as the provider identifier).

```typescript
// From encryptor.ts lines 21-23
static #makeKey(key: string): Buffer {
  return createHash('sha256')
    .update(`${Environment.cryptoSecret}${key}`)
    .digest()
    .slice(0, 32); // Truncate to 32 bytes for AES-256
}

```

This derivation strategy ensures that each provider credential uses a distinct encryption key, limiting the blast radius if a single key were compromised.

### AES-256-CBC Implementation

The core encryption logic resides in [`src/main/services/encryptor.ts`](https://github.com/nanbingxyz/5ire/blob/main/src/main/services/encryptor.ts) (lines 32-39). For every encryption operation, the system:

1. Generates a cryptographically random 16-byte IV
2. Creates an AES-256-CBC cipher using the derived key
3. Returns the IV (as hex) and ciphertext (as Base64)

```typescript
// Encryption implementation (lines 32-39)
static encrypt(text: string, key: string): EncryptedData {
  const iv = randomBytes(16);
  const cipher = createCipheriv('aes-256-cbc', this.#makeKey(key), iv);
  const encrypted = cipher.update(text, 'utf8', 'base64') + cipher.final('base64');
  return {
    iv: iv.toString('hex'),
    encrypted
  };
}

```

Decryption (lines 50-53) reverses this process using the stored IV and the same derived key, returning the original plaintext only in memory.

## Secure Storage Flow

### IPC Bridge Communication

The renderer process never handles raw encryption keys. Instead, it communicates with the main process through the bridge defined in [`src/main/bridge/encryptor-bridge.ts`](https://github.com/nanbingxyz/5ire/blob/main/src/main/bridge/encryptor-bridge.ts). This exposes `window.electron.crypto.encrypt` and `window.electron.crypto.decrypt`, which marshal requests to the main process where the `Encryptor` class executes.

```typescript
// Renderer-side usage pattern
const { iv, encrypted } = await window.electron.crypto.encrypt(
  apiKey,
  `provider-${userId}`
);

```

### Electron Store Persistence

The encrypted payload is persisted via `electron-store` in [`src/renderer/pages/providers/index.tsx`](https://github.com/nanbingxyz/5ire/blob/main/src/renderer/pages/providers/index.tsx) (lines 152-158). The code stores only the `iv` and `encrypted` fields, never the plaintext or the master secret.

```typescript
// From providers/index.tsx lines 152-158
await window.electron.store.upsert({
  id: userId,
  data: { iv, encrypted },
});

```

### System Keychain Integration

According to the **ARCHITECTURE.md** security section, 5ire supports storing the `CRYPTO_SECRET` itself in the OS keychain (macOS Keychain, Windows Credential Manager) rather than as an environment variable. This provides OS-level protection for the master encryption key, ensuring it never appears in the filesystem or process environment listings.

## Practical Implementation Example

The following pattern demonstrates how 5ire handles provider credentials in the UI layer, combining encryption, storage, and decryption:

```typescript
// Saving a new provider API key
async function saveProviderKey(userId: string, apiKey: string) {
  // Encrypt on the main process side via IPC
  const { iv, encrypted } = await window.electron.crypto.encrypt(
    apiKey,
    `provider-${userId}`
  );

  // Store only the encrypted blob
  await window.electron.store.upsert({
    id: userId,
    data: { iv, encrypted },
  });
}

// Retrieving the key for API calls
async function getProviderKey(userId: string): Promise<string> {
  const { iv, encrypted } = await window.electron.store.get(userId);
  
  // Decrypt only when needed, keep in memory briefly
  const plain = await window.electron.crypto.decrypt(
    encrypted,
    `provider-${userId}`,
    iv
  );
  return plain;
}

```

## Summary

5ire implements defense-in-depth for credential protection:

- **AES-256-CBC encryption** with per-provider key derivation prevents unauthorized decryption
- **Environment-based master secrets** (`CRYPTO_SECRET`) ensure installation-specific encryption
- **IPC isolation** keeps cryptographic operations in the main process, away from renderer vulnerabilities
- **Random IV generation** for every encryption operation prevents ciphertext pattern analysis
- **OS keychain support** provides hardware-backed storage for the master secret when available

## Frequently Asked Questions

### What encryption algorithm does 5ire use?

5ire uses **AES-256-CBC** symmetric encryption. The implementation in [`src/main/services/encryptor.ts`](https://github.com/nanbingxyz/5ire/blob/main/src/main/services/encryptor.ts) utilizes Node.js’s native `crypto` module, generating a random 16-byte IV for each encryption operation to ensure semantic security.

### Where is the encryption key stored?

The master encryption key (derived from `CRYPTO_SECRET`) remains in **process memory only**. According to the architecture documentation, the `CRYPTO_SECRET` environment variable can optionally be stored in the OS keychain (macOS Keychain or Windows Credential Manager), preventing the secret from ever residing in the application’s configuration files.

### Can encrypted API keys be decrypted by other 5ire installations?

No. Because the encryption key derivation incorporates the **per-installation** `CRYPTO_SECRET` environment variable, encrypted credentials stored by one 5ire instance cannot be decrypted by another installation unless both share the identical `CRYPTO_SECRET` value. This effectively binds credential encryption to the specific device and installation.

### Does 5ire store API keys in plain text at any point?

No. API keys are encrypted immediately in the main process via the IPC bridge before reaching the Electron store. The plaintext exists only transiently in memory during the active encryption or decryption operation, and the persistent storage at `~/.config/5ire` contains only the AES-256-CBC encrypted payload with its associated IV.