我之前写了个note类应用后,由于在项目中涉及到了key的明文存储,查了资料后发现safeStorage存储API,故记录一下。
在 Electron 应用中,使用 safeStorage API 加密存储 API Key 是官方推荐的安全加密手段,本质是利用操作系统提供的原生加密系统(如 macOS 的钥匙串、Windows 的 DPAPI)来保护敏感数据。
需要注意的是,safeStorage 仅可在主进程(Main Process)中使用

🔐 平台支持与安全说明

safeStorage 的安全性取决于底层操作系统:

  • macOS:密钥存储在你的应用专属的钥匙串中,可防止其他应用和用户访问。
  • Windows:使用 DPAPI 加密,通常只有加密时登录的同一用户才能解密。但无法防止同一用户下的其他应用访问。

🛠️ 基本用法

safeStorage API 提供了三个核心方法:

  1. safeStorage.isEncryptionAvailable()

    • 检查当前平台是否支持加密。调用任何加密方法前,务必先调用此方法进行检查
    • 在 Windows 上通常返回 true,macOS 需钥匙串可用。
  2. safeStorage.encryptString(plainText)

    • 加密明文字符串,返回一个 Buffer 对象。
  3. safeStorage.decryptString(encrypted)

    • 解密由 encryptString 生成的 Buffer,返回明文字符串。解密失败会抛出错误。

💻 在 Electron 应用中的实践

1. 存储与检索 API Key

下面是一个结合 electron-store(用于持久化存储)的 SecretStore 类实现,演示了完整的加密存储与解密读取流程。

// 在主进程 (main.ts) 中
import { safeStorage } from 'electron';
import Store from 'electron-store';

const store = new Store();
const ENCRYPTED_KEY = 'encryptedApiKey';

class SecretStore {
  // 加密并存储 API Key
  setApiKey(apiKey: string): void {
    // 1. 检查加密是否可用
    if (!safeStorage.isEncryptionAvailable()) {
      throw new Error('当前环境不支持安全存储');
    }

    // 2. 加密字符串
    const encryptedBuffer = safeStorage.encryptString(apiKey.trim());

    // 3. 将 Buffer 转为 Base64 字符串后存储
    store.set(ENCRYPTED_KEY, encryptedBuffer.toString('base64'));
  }

  // 解密并获取 API Key
  getApiKey(): string | null {
    // 1. 从存储中读取 Base64 字符串
    const encryptedBase64 = store.get(ENCRYPTED_KEY) as string | undefined;
    if (!encryptedBase64) {
      return null;
    }

    try {
      // 2. 将 Base64 字符串转回 Buffer
      const encryptedBuffer = Buffer.from(encryptedBase64, 'base64');
      // 3. 解密 Buffer
      return safeStorage.decryptString(encryptedBuffer);
    } catch (error) {
      // 4. 解密失败(可能数据已损坏),删除无效数据
      store.delete(ENCRYPTED_KEY);
      return null;
    }
  }

  // 其他方法:删除、检查是否存在等
  deleteApiKey(): void {
    store.delete(ENCRYPTED_KEY);
  }
  hasApiKey(): boolean {
    return store.has(ENCRYPTED_KEY);
  }
}

代码参考了相关实现。

2. Linux环境下,处理无密钥库的情况

为增强应用健壮性,建议在 Linux 上检测后端存储类型:

import { safeStorage } from 'electron';

// 检查当前使用的存储后端
const backend = safeStorage.getSelectedStorageBackend();
if (backend === 'basic_text') {
  // 提示用户当前环境不支持安全存储,加密是无效的
  console.warn('警告:当前 Linux 环境未检测到安全密钥库,API Key 将以明文形式存储!');
}

getSelectedStorageBackend() 的返回值可参考官方文档。

3. 进程隔离与安全实践

为确保安全,切勿将明文 API Key 发送到渲染进程,应遵循以下架构:

  1. 主进程 (Main):负责所有加密、解密和持久化存储操作。
  2. 预加载脚本 (Preload):通过 contextBridge 向渲染进程暴露安全的、受限制的 API。
  3. 渲染进程 (Renderer):通过预加载脚本暴露的 IPC 桥接器,请求主进程执行操作(如保存、获取 Key),仅接收操作结果或脱敏后的 Key(如 sk-...ab12)。

📦 第三方库:genai-key-storage-lite

若不想从零实现加密存储,也可以使用封装好的库,如 genai-key-storage-lite。它基于 safeStorage 提供了更便捷的 API,并内置了进程隔离方案,无需考虑细节,直接调用就可以了。

核心用法:

// 1. 主进程:初始化服务并注册 IPC 处理器
// main.ts
import { ApiKeyServiceMain, registerSecureApiKeyIpc } from 'genai-key-storage-lite';
const apiKeyService = new ApiKeyServiceMain(app.getPath('userData'));
registerSecureApiKeyIpc(apiKeyService);

// 2. 预加载脚本:创建安全桥接器
// preload.ts
import { createApiKeyManagerBridge } from 'genai-key-storage-lite/preload';
contextBridge.exposeInMainWorld('electronBridge', {
  secureApiKeyManager: createApiKeyManagerBridge(),
});

// 3. 渲染进程:通过桥接器调用
// renderer.ts
const bridge = window.electronBridge.secureApiKeyManager;
await bridge.setApiKey('your-api-key-here'); // 保存
const key = await bridge.getApiKey();       // 获取明文 (仅在主进程解密)
const masked = await bridge.getApiKeyMasked(); // 获取脱敏Key

💎 总结

使用 Electron 的 safeStorage 加密存储 API Key 的核心步骤是:

  1. 检查环境:调用 safeStorage.isEncryptionAvailable()
  2. 加密存储:在主进程使用 safeStorage.encryptString() 加密,将结果 Buffer 转为 Base64 字符串后,用 electron-store 等工具持久化。
  3. 解密读取:读取 Base64 字符串并转为 Buffer,再用 safeStorage.decryptString() 解密。
  4. 隔离进程:通过预加载脚本暴露安全 API,确保明文 Key 不进入渲染进程。
Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐