ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Electron safeStorage API 详解:用系统级加密安全存储密码与敏感数据

Electron safeStorage API 详解:用系统级加密安全存储密码与敏感数据 Electron safeStorage API 详解用系统级加密安全存储密码与敏感数据【免费下载链接】electron:electron: Build cross-platform desktop apps with JavaScript, HTML, and CSS项目地址: https://gitcode.com/GitHub_Trending/el/electronsafeStorage是 Electron 主进程中用于加密存储字符串的内置模块它借助操作系统自带的密码学系统macOS Keychain、Windows DPAPI、Linux 密钥环对数据落盘前进行加密使密钥本身不随应用分发。本文基于 Electron 仓库的 safeStorage 官方文档 与 C 实现源码 展开覆盖同步/异步两套 API 的完整方法签名、各平台密钥机制与安全性差异并结合测试用例给出跨应用重启持久化的实战写法帮助你在 Electron 应用中正确落地敏感数据本地加密存储这一通用需求如保存 OAuth token、API 密钥、登录凭据。1. 模块定位主进程专属的 OS 级加密层safeStorage只能从主进程Main Process访问。它的价值在于加密密钥由操作系统托管而不是写死在代码里——macOS 上密钥存入用户 KeychainWindows 上通过 DPAPI 按登录凭据保护Linux 上存入 KWallet / GNOME Keyring 等密钥环。从源码结构看整个模块只有一层薄薄的 JS 封装lib/browser/api/safe-storage.ts 仅 3 行直接process._linkedBinding(electron_browser_safe_storage)取到 C 侧的SafeStorage单例全部逻辑实现在 shell/browser/api/electron_api_safe_storage.cc 中并作为electron模块的顶层属性暴露给主进程。文档明确推荐优先使用异步 APIencryptStringAsync/decryptStringAsync异步 API 非阻塞支持密钥轮换key rotation并能妥善处理密钥临时不可用的场景同步 API 可能在未来的 Electron 版本中被弃用。在 macOS 上访问系统 Keychain 可能需要阻塞当前线程以收集用户输入在 Linux 上若存在密码管理工具同样可能出现交互式解锁。2. 平台密钥机制同步 API 与异步 API2.1 同步 API 的密钥来源平台密钥托管方式安全边界macOS密钥存入 Keychain Access其他应用无法在无用户授权的情况下读取内容受保护可抵御同一用户空间内的其他用户与其他应用Windows密钥由 DPAPI 生成按 Windows 文档通常只有持有加密数据用户的相同登录凭据的用户才能解密数据可抵御同一机器上的其他用户但不能抵御同一用户空间内的其他应用Linux密钥生成并存储于随窗口管理器/系统配置变化的密钥环中当前支持kwallet、kwallet5、kwallet6、gnome-libsecret安全性随密钥环实现不同而变化Linux 上有一个重要的降级风险并非所有 Linux 环境都有可用的密钥环。当没有可用密钥环时safeStorage加密的数据实际上是用硬编码的明文口令加密的等同于未受保护。可通过safeStorage.getSelectedStorageBackend()返回basic_text来检测这一情况见 第 5.4 节。macOS 代码签名要求文档标注为 IMPORTANT在 macOS 上应用必须经过代码签名safeStorage的行为才能保持一致。没有有效且一致的签名时macOS 可能无法将不同构建识别为同一应用导致每次更新后 Keychain 都重新向用户弹出授权确认。2.2 异步 API可插拔的 Key Provider异步 API 采用按平台切换的可插拔密钥提供器pluggable key providersmacOS密钥从 Keychain 存取安全模型与同步 API 相同Windows密钥受 DPAPI 保护安全模型与同步 API 相同Linux根据桌面环境可能同时存在多个提供器org.freedesktop.portal.Secret走 Portal Secret D-Bus 接口获取应用专属密钥是 Flatpak 等沙箱环境的首选提供器Secret Service API使用 freedesktop.org Secret Service API如 GNOME Keyring兜底提供器fallback provider用于完全没有密钥服务的环境。与同步 API 相比异步操作非阻塞并支持两个同步 API 不具备的能力密钥轮换解密结果的shouldReEncrypt字段指示与临时不可用处理isTemporarilyUnavailable指示Promise 会以 temporarily unavailable 错误拒绝稍后重试即可。3. 方法全解3.1safeStorage.isEncryptionAvailable()返回boolean——加密是否可用。各平台的判定条件Linux应用已发出ready事件且密钥可用时返回true若开启了明文加密降级且后端为basic_text也返回true见 electron_api_safe_storage.cc#L177-L188macOSKeychain 可用时返回trueWindows应用已发出ready事件后返回true。注意在app发出ready事件之前调用该方法会返回false此时若直接调用encryptString会抛出safeStorage cannot be used before app is ready。3.2safeStorage.isAsyncEncryptionAvailable()返回Promiseboolean。异步加密器是懒加载的在应用 ready 之后首次调用isAsyncEncryptionAvailable、encryptStringAsync或decryptStringAsync时才会触发初始化Promise 在初始化完成后 resolve。从 C 实现 看懒加载的动机写得非常清楚——ESM 具名导入会急切求值所有 electron 模块的 getter如果在构造函数中就去请求操作系统密钥环那么哪怕应用从未使用过safeStorage也会触碰系统 Keychain。初始化流程是EnsureAsyncEncryptorRequested()通过g_browser_process-os_crypt_async()-GetInstance(...)异步获取加密器实例就绪回调OnOsCryptReady统一冲刷挂起的请求队列见 第 4 节。3.3safeStorage.encryptString(plainText)与safeStorage.decryptString(encrypted)encryptString(plainText: string) → Buffer返回加密后的字节数组加密失败时抛出错误。decryptString(encrypted: Buffer) → string把encryptString产生的密文还原为字符串。两个同步方法在实现上有若干值得注意的行为electron_api_safe_storage.cc#L232-L308ready 检查app未 ready 时抛出safeStorage cannot be used before app is ready版本前缀校验decryptString会检查密文是否以v10或v11前缀开头源码常量kEncryptionVersionPrefixV10/V11见 electron_api_safe_storage.cc#L29-L30前缀不符直接抛出Ciphertext does not appear to be encrypted.——这样能在 macOS/Linux 上把解密密文被破坏/传错从静默失败变成显式错误类型检查decryptString的入参必须是 Buffer非 Buffer 会抛Expected the first argument of decryptString() to be a buffer。以上错误路径都有对应测试覆盖见 spec/api-safe-storage-spec.ts#L56-L81。3.4safeStorage.encryptStringAsync(plainText)与safeStorage.decryptStringAsync(encrypted)encryptStringAsync(plainText: string) → PromiseBuffer异步加密返回密文字节。decryptStringAsync(encrypted: Buffer) → PromiseObject返回对象包含shouldReEncrypt: boolean——如果为true说明密钥已轮换或新密钥提供了不同的安全级别应当再次调用decryptStringAsync以获得用新密钥对应的解密结果并重新加密存储result: string——解密后的明文。decryptStringAsync的错误语义electron_api_safe_storage.cc#L342-L397入参不是 Buffer → rejectExpected the first argument of decryptStringAsync() to be a buffer密文为空 Buffer → resolve{ shouldReEncrypt: false, result: }不报错密钥临时不可用 → rejectsafeStorage.decryptStringAsync is temporarily unavailable. Please try again.业务侧应做重试其他解密失败 → rejectError while decrypting the ciphertext provided to safeStorage.decryptStringAsync.。一个容易被忽视的事实是同步与异步密文互通官方测试 spec/api-safe-storage-spec.ts#L188-L208 专门验证了encryptString产生的密文可被decryptStringAsync解开、encryptStringAsync产生的密文可被decryptString解开且多路并发异步加解密结果正确。这意味着你可以按迁移节奏逐步从同步 API 切换到异步 API而不需要一次性重加密所有存量数据。3.5safeStorage.setUsePlainTextEncryption(usePlainText)usePlainText: boolean。该方法是Linux 专属开关当无法为当前活跃的桌面环境确定一个有效的操作系统密码管理器时强制模块改用内存中的明文口令派生对称密钥来完成加解密。在 Windows 和 macOS 上是 no-op空操作。实现上它对应 C 侧的SetUsePasswordV10仅记录一个标志位electron_api_safe_storage.cc#L219-L221该标志会影响isEncryptionAvailable与isAsyncEncryptionAvailable在 Linux 上对basic_text后端的判定。使用它的典型场景是无密钥环的 CI/容器环境——Electron 自身测试套件在 Linux 上就无条件开启了它spec/api-safe-storage-spec.ts#L17-L21。3.6safeStorage.getSelectedStorageBackend()Linux返回string表示 Linux 上实际选中的密码管理器。源码中的后端枚举与字符串一一对应见 browser_process_impl.cc#L415-L438 的SetLinuxStorageBackend。可能返回值返回值触发条件basic_text桌面环境无法识别或提供了命令行参数--password-storebasicgnome_libsecret桌面环境为X-Cinnamon、Deepin、GNOME、Pantheon、XFCE、UKUI、unity或提供了--password-storegnome-libsecretkwallet桌面会话为kde4或提供了--password-storekwalletkwallet5桌面会话为kde5或提供了--password-storekwallet5kwallet6桌面会话为kde6或提供了--password-storekwallet6unknown在app发出ready事件之前调用该函数对生产应用的一个实用建议在ready后调用该方法一旦发现返回basic_text就应提示用户/日志告警——此时磁盘上的密文实际上只受硬编码口令保护。4. 源码级实现细节懒加载、挂起队列与版本前缀阅读 shell/browser/api/electron_api_safe_storage.cc 可以确认几处文档行为背后的机制单例与懒初始化SafeStorage::Create返回 cppgc 托管的每 isolate 单例异步加密器直到首次使用才通过EnsureAsyncEncryptorRequested()请求L98-L108避免未用 safeStorage 也触碰系统 Keychain。挂起队列pending queue加密器未就绪时encryptStringAsync/decryptStringAsync/isAsyncEncryptionAvailable不会立即失败而是把 Promise 连同入参存入pending_encrypts_/pending_decrypts_/pending_availability_checks_OnOsCryptReady回调到达时统一执行并冲刷队列L110-L162。这解释了为什么应用刚 ready 就并发发起多次加解密是安全的。密文版本前缀v10/v11前缀让decryptString能快速拒绝非本模块产生的数据把数据损坏/用错密文变成显式异常而非静默返回错误明文。shouldReEncrypt的来源异步解密结果直接透传 Chromiumos_crypt_async::Encryptor::DecryptFlags中的should_reencrypt与temporarily_unavailable标志L374-L392即密钥轮换语义来自底层 OS Crypt 异步组件。5. 实战模式5.1 基础用法保存与读取一条敏感字符串const { app, safeStorage } require(electron); const fs require(node:fs); const path require(node:path); const CRED_PATH path.join(app.getPath(userData), credentials.enc); app.whenReady().then(async () { // 推荐先确认可用性再读写 if (!safeStorage.isEncryptionAvailable()) { console.error(无法加密存储检查平台密钥环是否可用); return; } // 写入写入前检查是否为降级后端 if (process.platform linux safeStorage.getSelectedStorageBackend() basic_text) { console.warn(未检测到密钥环数据将以弱保护方式存储); } const token your-secret-token; const encrypted safeStorage.encryptString(token); // Buffer fs.writeFileSync(CRED_PATH, encrypted); // 读取 const decrypted safeStorage.decryptString(fs.readFileSync(CRED_PATH)); console.log(decrypted token); // true });5.2 推荐写法异步 API 密钥轮换处理async function loadSecret() { const { safeStorage } require(electron); const fs require(node:fs); const encrypted fs.readFileSync(CRED_PATH); let dec await safeStorage.decryptStringAsync(encrypted); if (dec.shouldReEncrypt) { // 密钥已轮换再次解密拿到与当前密钥一致的结果并重写存储 dec await safeStorage.decryptStringAsync( await safeStorage.encryptStringAsync(dec.result) ); fs.writeFileSync(CRED_PATH, await safeStorage.encryptStringAsync(dec.result)); } return dec.result; }对 temporarily unavailable 的拒绝错误建议配合退避重试对shouldReEncrypt的处理则保证了密钥轮换后存量数据自动升级。5.3 跨应用重启的密钥持久性验证safeStorage承诺的关键性质是加密密钥在应用退出后依然持久由 OS 密钥环托管因此一次加密的数据在下次启动后仍可解密。Electron 测试套件用两个最小应用验证了这一点spec/api-safe-storage-spec.ts#L210-L249加密应用 spec/fixtures/api/safe-storage/encrypt-app/main.jsapp.whenReady()后safeStorage.encryptString(plaintext)写入encrypted.txt随后app.quit()解密应用 spec/fixtures/api/safe-storage/decrypt-app/main.js另起一个新进程读回encrypted.txtdecryptString后输出明文断言输出包含plaintext。这两个 fixture 可直接作为你自己项目里持久化凭据的最小参考实现——注意两者在 Linux 上都先调用了setUsePlainTextEncryption(true)以便在无密钥环的测试环境中跑通。6. 使用清单与常见误区时机所有方法都应在app的ready事件之后使用过早调用会抛错同步或 reject异步。API 选型新代码优先encryptStringAsync/decryptStringAsync处理shouldReEncrypt与临时不可用同步 API 注意其未来可能弃用。跨平台心智模型macOS 的隔离性最强Keychain 阻止同用户空间其他应用读取密钥Windows 的 DPAPI 只能隔离其他用户同用户空间内的其他进程原则上能拿到相同密钥——不要把 safeStorage 当作进程间防窃取手段它是防离线读取文件的保护。Linux 兜底检测始终用getSelectedStorageBackend()识别basic_text降级并向用户明示数据保护级别。macOS 签名未完成代码签名的构建会导致每次更新后 Keychain 反复弹授权应把签名纳入发布流程。能力边界该模块只加密/解密字符串底层返回Buffer不做密钥派生或密码哈希decryptString遇到未加密数据会抛错而不是静默返回原串可安全地用它做是否密文的防御性校验。参考路径汇总API 文档 docs/api/safe-storage.mdC 实现 shell/browser/api/electron_api_safe_storage.cc 与 shell/browser/api/electron_api_safe_storage.hJS 包装 lib/browser/api/safe-storage.tsLinux 后端选择 shell/browser/browser_process_impl.cc测试 spec/api-safe-storage-spec.tsmacOS 签名背景 docs/tutorial/code-signing.md。【免费下载链接】electron:electron: Build cross-platform desktop apps with JavaScript, HTML, and CSS项目地址: https://gitcode.com/GitHub_Trending/el/electron创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表