@byokit/ 机密
API 键的应用程序每个名称有一个秘密 : get/set/delete(name)。平台商店加备好节点封接适配器:
import { fileStore, keyringStore, overrideStore } from '@byokit/secrets';
// The OS keyring: macOS Keychain or Secret Service (libsecret).
const keys = keyringStore(); // { service?, bin?, tool?, env?, timeoutMs? }
await keys.set('openai', 'sk-…');
await keys.get('openai'); // 'sk-…' | null
await keys.delete('openai'); // true when an entry existed
// A passphrase-sealed file, sealed with @byokit/seal.
async function saveToFile(passphrase: Uint8Array) { // supplied by the person through the host app
const file = fileStore({ path: '/home/app/data/keys.json', passphrase });
await file.set('openai', 'sk-…');
}
// CI: the host builds the map from process.env itself; the kit never reads it.
const ci = overrideStore({ openai: process.env.OPENAI_API_KEY ?? '' });
默认节点条目需要节点22.18+. 浏览器和反应本地条件选择可移植
没有运行时间节点导入的条目。 明确条目为: @byokit/secrets/node, (中文(简体) ),
@byokit/secrets/web 财务报告和审定财务报表 @byokit/secrets/native。所有条目导出 Keystore, (中文(简体) ), KeystoreError
财务报告和审定财务报表 overrideStore;平台后端仅在其平台上被导出.
// Web / PWA (also available from the main entry under the browser condition).
import { webStore } from '@byokit/secrets/web';
const web = webStore({ database: 'my-app-keys' });
await web.set('openai', 'sk-…');
// React Native (also available from the main entry under the react-native condition).
// Install the optional peer in the host app: npx expo install expo-secure-store.
import { nativeStore } from '@byokit/secrets/native';
const phone = nativeStore({ prefix: 'my-app' });
await phone.set('openai', 'sk-…');
后端
原生 OS 密钥环 (单位:千美元)osKeyringStore({ service }). macOS keychain, Windows 证书管理器和
Linux 特勤局通过可选的,完全固定 @napi-rs/keyring 2.1.0 N-API 装订.
同步形式 osKeyring({ service }) 执行 KeyringBackend 封存; 存储表单
执行常见的 Aync API。 绑定的负载只在操作中进行. Linux 明确要求
{ linux: { store: 'secret-service' } }:不倒向内核键环,在重启时会消失.
缺失/ 锁定服务或缺少本地二进制投放 KeystoreError 与 code: 'unavailable'。 。 。 .
只有实际缺席的条目返回无效/虚假 。 不支持的OSs 抛出 unsupported。 。 。 .
名称,服务和价值在本地呼叫之前被验证;本地错误被消毒.
与CLI后端不同,以LF结尾的值被保留.
OS 密钥环 (单位:千美元)keyringStore. macOS 键链通过 /usr/bin/security,通过
/usr/bin/secret-tool每一个产物 绝对路径。 秘密只通过Stdin到达CLI,从未进入
argv或env;每个产卵都得到一个由一无所取而成的env(PATH, (中文(简体) ), LANG加上主机通过的东西
env (在Linux那里) DBUS_SESSION_BUS_ADDRESS 说吧. get 将单行径 LF 脱去 CLI
当它打印出一个秘密时会添加,所以一个秘密结束在一个没有它的新线回转中. 窗口证书
V1 中的管理器不支持 : win32 每个电话都拒绝 unsupported。 。 。 .
密码句文件 (单位:千美元)fileStore) (中文(简体) ). 整个地图都用一个JSON文件
{ v: 1, kdf: 'scrypt-16384-8-1', salt, box } 地点 box 实值 sealSecretBox 超过 a 条目
克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克克 写入通过导出 writeFileAtomic (0700个)
文件夹, 0600 文件, 重命名目标). 错误的密码句拒绝 auth-failed 未关闭 :
无所归而无所出. 主张a Uint8Array 密码句: 字符串不能被零化
因为运行时间保存副本; 生成的密钥在使用后被零.
CI 覆盖 (单位:千美元)overrideStore) (中文(简体) ). 经核实的东道主通行证地图副本; get/set/delete 二、会议工作
copy. 在CI中,主机从 process.env 工具箱从不读取环境
毒害的环境不能改变药箱的作用.
电话 (单位:千美元)nativeStore) 调用 Expo SecureStore 的 assync 获取/ set/ 删除方法。 可选
expo-secure-store 在第一个操作上对等加载; 导入条目不会装入本地代码 .
一个主机可以提供 secureStore 采用相同的三种测试方法或另一种兼容的安全
后端 . options 通过安全斯托尔 keychainService、无障碍、认证和访问
每一个呼叫都一致选择。 原生的失败被隔离 failed 错误; 缺少
拒绝对等 unavailable。没有模拟平白文字倒置。 名称被编码为固定宽度
UTF-16 六进制 prefix (默认) byokit)满足了"博览会"的关键字母表而不相撞.
SecurityStore可以拒绝大值,甚至低于工具包的 1 MiB 限制; 认证保护值
生物特征改变后,无法进入。 见 世博会的安装和持久性指南。 。 。 .
原删除在删除前读取;同时调用者可能都报告为真实.
网页 (单位:千美元)webStore) (中文(简体) ). 需要安全浏览器上下文( HTTPS 或可信赖的本地主机), IndexedDB
还有WebCrypto. database 默认为 byokit-secrets; 每个应用程序在共享上使用不同的数据库
源代码。 这个 wrapped 对象存储在结构化可复制的 AES-256-GCM 密钥下
device-wrap-key,并在 item:<name>。每集生成新的12字节IV;
jSON 编码条目名称被认证为额外数据。 秘密值也是JSON编码
以保留每个 JavaScript 字符串,包括未匹配的 UTF-16 代词. 初始化选择/插入一个密钥
重写交易, 因此同时设置标签保存一键 。 只有在交易完成后才写解;
删除检查的存在,并删除一项交易中的条目。 坦佩雷密码, 互换条目
名称和无效的密钥拒绝 auth-failed。浏览器存储/crypto 失败被解密拒绝
出错; 没有正文倒置 。 可注射 indexedDB, (中文(简体) ), crypto 财务报告和审定财务报表 isSecureContext 选项
只能做假测试 操作后没有密钥缓存: 清除源存储需要登录
再来一次 这保护了密码文本的休息, 不针对同源脚本或 XSS,可以使用
存储用于解密的密钥。 浏览器配置文件复制和存储驱逐仍然是浏览器/操作系统的关切.
现有产品商店不自动进口. 要迁移,主机读取每个条目 它的旧商店,通过这个API设置,验证它,然后去掉旧条目. 土著名称和网络 密码文本有工具箱自有格式,所以指向一个老应用程序的数据库不是迁移.
错误
KeystoreError 与 code数字 : invalid (坏名字、秘密、路径、密码句或选项——消息名称
进入,绝不是秘密, keyring-locked (密封密钥环证书暂时无法访问;解锁后重试), auth-failed (错误的密码/密钥、被篡改的文件或网络密码/密钥), unsupported (窗口 CLI 密钥环)
或无支持的本地操作系统, unavailable (丢失/无法访问本地密钥环、 CLI、 主机密钥、 博览会端点或浏览器 API), failed (任何其他:平台)
出错,超时,输出过大,无法读取的文件). 缺少的条目不是错误 : get 解决无效和
delete 解决错误.
桌面上的封存账户文件
import { fileStore } from '@byokit/accounts';
import { osKeyringSeal } from '@byokit/secrets/node';
const seal = osKeyringSeal({ service: 'Umer-desktop' });
const accounts = fileStore('/app-owned/private/accounts.bin', seal);
// Or directly: fileStore(path, osKeyringSeal({ service: 'Umer-desktop' })).
使用该应用程序本身的绝对路径,拥有一个私有的0700父目录. 账户实际接合0.8.0
实值 fileStore(path, adapter); 适配器同步执行 encryptString/decryptString。 。 。 .
在节点或Electron的主要过程中运行此功能. 原生电话可能会阻断或触发OS授权UI;
让他们远离渲染器/请求延迟路径。 每项服务必须是应用程序/安全环境所特有的.
osKeyringSeal 构建时尝试非交互式 OS 密钥环访问 。 如果可用,则创建
在第一个写入时随机32字节的数据密钥并存储在OS密钥环中. 如果服务缺席,
锁定或无响应,它自动使用下面描述的持久主机密钥文件.
seal.mode 报告当前写入模式( 成功读取了存储模式) : 'keyring', (中文(简体) ), 'host-key-file' 或选入 'dual-wrap'· ; hostKeySeal().mode 实值 'host-key'。 。 。 .
过 fallback: false 当键盘是强制的。 一旦文件密钥存在,它仍为模式
即使以后有键盘可用 , 也可以给服务写新字 . 阅读总是跟随商店
标题:模式1使用密钥环,模式2使用主机文件密钥,无论写入模式或一种
已存在的后退目录。 在密钥环被锁定或没有响应时打开模式-1商店
已消毒 KeystoreError 代码 keyring-locked没有 auth-failed; 它保持不变。 重试读取
解锁后(同一适配器工作). @byokit/openclaw 决定 prepare()编号 :start() 在一个 locked
以简洁的言语,以及后来 start() 重试. 它不会以空商店启动助手 .
供应商的全权证书仍保存在密封账户档案中.
自动按键环选择在短命的帮助器中运行操作,超时1000毫秒
(单位:千美元)timeoutMs, 100–5000毫秒). 关键材料穿行于私人的stdin/sdout管上,从不争论或
儿童环境变量。 电子助手使用 ELECTRON_RUN_AS_NODE=1; 禁用的主机
引信应选择 hostKeyFileSeal 直接说 Linux 使用一个完全被绑定的 D- Bus 客户端和现有的会话总线 :
没有服务激活、收藏创建、解锁或即时呼叫。 锁定的项目和收藏失败
关闭。 它只读 DBUS_SESSION_BUS_ADDRESS 用于该会话(否则为标准用户总线).
Windows使用本地证书管理器后端. 被钉住的 macOS 原生约束无法抑制
授权 UI, 因此自动选择使用文件密钥 ; 明文 osKeyring() 遗 物
可用于允许交互的主机。 喷出 keyring 后端必须被绑定
和不互动。 套件从不配置或解锁人物的密钥环.
双层外接
import { osKeyringSeal } from '@byokit/secrets/node';
const seal = osKeyringSeal({ service: 'my-app', dualWrap: true });
默认 关。当密钥环可以访问时,每个模式-3信封会包裹一个新的随机有效载荷
键,其中既包含 OS 密钥环,也包含 持久所有者唯一的主机文件密钥。 锁定或未响应
密钥环可以通过主机包打开这个信封而无需提示. 有效载荷认证
包装信封 长度 外头和服务. 双打商店只是
和唯一所有者主机密钥文件一样坚固,即使OS键盘被锁定。 排除主机密钥
包含密码文本的备份/曝光边界; 同用户代码和全磁盘的目录
包含密钥和存储的复制件可以解密. dualWrap: true 与 fallback: false 无效.
在解锁时读取一个已有的只按键环存储器, 并解锁后将其升级为双接式
账户 fileStore 或 OpenClaw 软件 authSeal. 认证/验证和解密平等检查
替换前; 替换失败或中断后, 原始密钥/ 存储可用 .
当多个进程共享一个商店时, 持有应用程序的独家写手锁 。 联合国其他消费者
适配器必须称为可选 upgrade(bytes) 在验证已解密的内容后, 验证平等性,
并在解剖学上取代文件本身; decryptString 单曲不改变磁盘.
一个新建的没有密钥环访问的适配器仍然为新商店选择主机-密钥文件.
成功读取为后续写取的存储模式, 所以仅存密钥环存储
仅保留按键环,双子店则保留双子店。 在锁定时, 双重更新重用只验证
用新有效载荷非ce加密包裹元数据; 纯文本密钥永远不会被缓存 。 创建
新的双面包装需要密钥环访问. 没有自动重写锁定的只键环商店 .
双 rotate(paths) 创建新的密钥环来包装密钥并重新封存所提供的商店,保留
用于备份的前置密钥。 它不退出或旋转共享主机文件包密钥 。 分组旋转混合模式存档 .
信封和密钥环旋转
这个信封是 BKS1 | mode (1 byte) | key id (16 bytes) | sealSecretBox payload,使用
@byokit/seal'''s XSalsa20-Poly1305 有新鲜的24字节活塞. 加密有效载荷重复
整个标题并持有 JSON { service, text }; 页眉和应用程序上下文都经过认证.
版本更改、错误的密钥、截取、篡改或丢失/损坏密钥条目 auth-failed。 。 。 .
与 fallback: false,缺少本地存储 unavailable。没有简单的倒叙
或者在解密时替换密钥生成。 本地装订从不看文件的证书 .
seal.rotateKey() 创建并验证新密钥,然后激活它用于后续的写作.
旧密钥留在密钥环中, 因此以前的文件和备份仍然打开 。 每个不可改变的密钥都有
随机条目名称,避免当两个进程同时初始化/旋转时丢失密钥; 最后一个
活动 - id 更新获胜 。 启动失败可能留下未使用的密钥 。 实例重读活动
每次写作时的ID,所以其他过程会取回旋转. 仅旋转不会重写文件 :
通过账户重写,同时持有主机的写作锁,验证,再退出旧备份.
要取消旧密钥, 主机会删除 byokit-seal-key-v1-<id> 结束
osKeyring({ service }).delete(name) 只有在使用它的所有文件被迁移或退出之后.
删除会永远阻止这些备份打开. 指针是 byokit-seal-active-v1· ;
绝不删除当前活动的数据密钥。 文件编写器仍然需要一个跨进程的主机锁.
具有约束力的选择和安全审查
我们评价了平台CLI和原生约束. 现有的CLI商店仍然可供
明确由主机控制的二进制/环境,但其接口为Async并缺少Windows.
维持 N- API 密钥环绑定 用品
用于桌面架构和直接本地API的预建二进制,没有外壳/argv秘密;
N-API避免了每电子版本的ABI重建. 内容 平台依赖关系
使用 Linux 上的 libsecret 所使用的 Keychain, 证书管理器和相同的特务协议 .
我们把2.1.0,谁的 Linux 商店选择 API
让我们需要坚持不懈的特勤局。 我们重用这个工具箱的原始密码
而不是引入另一种算法或执行加密。 在本地路径上缺少可选约束失败
关闭( 自动选择返回文件密钥); 主机必须将其平台二进制与 Electron 捆绑并保存 .node aSAR以外的文件.
- 其他本地用户: OS账户隔离保护密钥环数据; 账户的私人目录/文件 也限制进入。 私自攻击者和恶意软件运行,因为同一个解锁用户可以询问 键盘以解密或检查进程内存;这些适配器不会阻止它们.
- 已漏出的文件和备份: 密码文本单独揭示长度/版本/密钥 id,但没有 API 密钥. 恢复它需要原始的应用服务并保留OS密钥环数据(或主机密钥). 复制密钥环材料和文件都能够击败这种保护;OS备份策略为主机所拥有.
- Tampering 在账户读取或写入证书前无法认证 。 完整重放一个 旧的有效文件仍然是可能的;这个适配器不提供回滚/新鲜的保证.
- 拥有的字节副本在使用后为零; JavaScript 字符串, 本地副本和返回的纯文本 无法可靠地抹去。 永远不要日志密钥、证书或被解密的内容.
测试像主机应用程序一样驱动已建软件包: test/journey-secrets.test.ts 导入已出版的
条目 @byokit/secrets, (中文(简体) ), @byokit/secrets/web, (中文(简体) ), @byokit/secrets/native, (中文(简体) ), @byokit/accounts 财务报告和审定财务报表 @byokit/seal, (中文(简体) ),
惟有虔诚者 esbuild 财务报告和审定财务报表 fake-indexeddb,加3个测试助手(假键键CLI,真键后卫和
抓取 dirs),跨进程大小写运行构建 dist/index.js。假密钥环 CLIs 保留所有者的密钥环
无所未有. 一个真正的 Linux 测试是选择通过 sh scripts/test-keyring.sh,它清除环境和
创建私人 Home, XDG 树, D- Bus 和守护进程控制目录 。 测试说公交车是私人的
在本地调用之前拒绝继承的桌面设置。 设置 BYOKIT_REAL_KEYRING 单靠自己无法授权真正的测试.
没有可用的特勤局,选入测试会跳过,除非CI需要提供服务.
CI运行着同样的孤立的绳索. 行程包括按键环搜索和标签范围界定、按键托尔
读取/写出/删除、封存和解封错误的密钥、被篡改和被截断的输入,
进程- env 隔离, 无法解锁或写入的锁定密钥环, 以及被锁定的
权限运行后, 将会为字节留下一个诱饵 Home 字节 。 macOS/Windows 本地运行时间资格
仍为主机/平台CI工作.
服务器和无头节点
osKeyringSeal({ service: 'my-app' }) 在无头装置上不手工提供工作.
hostKeyFileSeal({ service }) 直接选择自动文件适配器。 两者都使用32个随机
字节从 crypto.randomBytes,在一个包含0600文件的私人0700目录中(Windows安装和
通过平台 ACL API 检查只拥有的 ACL, 如果无法关闭则无法关闭 .
拒绝现有的符号链接、外国所有者或团体/世界可访问的关键文件/目录
带有明显的错误; 工具包从不默默地修正其权限或替换被损坏的密钥.
唯一 O EXCL 临时文件在重命名前为 fsynced; 首先使用 发布完整的目录
在解剖学上,赛车过程有一个关键。 POSIX 目录也是fsynced;节点没有
提供Windows上的目录 fsync.
稳定的道路是 <platform state dir>/byokit-<SHA-256 of service>/host-key/状态目录
实值 XDG_STATE_HOME (绝对)或 ~/.local/state 在Linux上, ~/Library/Application Support 在macOS上,
财务报告和审定财务报表 %LOCALAPPDATA% 或者说 ~/AppData/Local 在视窗上。 每个应用程序使用稳定服务. stateDir 能够
覆盖平台状态根,例如在测试中;将其与证书备份相分离.
这个 active 指针名称不可变 <id>.key 文档。 从不手工删除或编辑这些内容.
信封使用模式2并认证了密钥ID和服务.
威胁模式 : 同一磁盘上的主机密钥保护被封存的商店免受复制、备份和 档案泄露 仅在密钥目录被排除时。它不保护代码 运行为同一 OS 用户,包含密钥和存储的全盘副本,或特权 攻击者。 失去钥匙使得商店无法恢复. 主机管理的密钥可以保持更强大的 通过住在外部的秘密经理人中而分居.
旋转自动主机密钥
停止引擎并持有应用程序的独家编剧锁, 在所有进程, 适配器创建中 旋转 使用此服务提供所有已封存的仓库并保留归档 :
import { hostKeyFileSeal } from '@byokit/secrets/node';
const seal = hostKeyFileSeal({ service: 'my-app' });
seal.rotate(['/app/private/accounts.bin', '/app/private/auth-store.sealed']);
旋转验证每个输入, 持久保存一个新键和一个 rotation 日记,然后
原子重写每个文件。 只有在所有替换都持久后,它才会激活新的
键,删除旧的并删除日记。 它暴露出没有键字节. 旋转中断
保留日记和足够打开两代的密钥;之后的加密拒绝,直到
seal.rotate() (无参数)恢复写入锁下记录的交易. 解密
康复期间仍在工作。 不编辑/删除日记或更改/保存所列文件
在康复期间。 列表中省略的文件,包括旧的备份,在之后无法读取
退休。 退休. rotateKey() 保留其现有的仅按键环行为; 自动文件密钥需要
rotate(paths) 因此退休不能在商店重写之前.
对于明确管理的密钥,使用 hostKeySeal。主机从它的
机密管理器或与账户数据分开保存的应用程序 0600 密钥文件:
import { readFileSync } from 'node:fs';
import { fileStore } from '@byokit/accounts';
import { hostKeySeal } from '@byokit/secrets/node';
// Host provisions this raw 32-byte key separately, with 0600 permissions.
const seal = hostKeySeal({ key: readFileSync('/host-managed/keys/Umer.key'), service: 'Umer-server' });
const accounts = fileStore('/app-owned/private/accounts.bin', seal);
// A synchronous resolver is also supported:
// hostKeySeal({ key: () => hostSecretCache.current32ByteKey(), service: 'Umer-server' })
Async秘密管理客户端在构建同步适配器之前必须解析/缓存密钥.
适配器复制每次操作的主机密钥并给自己的副本零;它永远不会去除主机的
字节。 解析器失败抛出消毒器 unavailable; 键长无效 invalid。 。 。 .
主机密钥信封采用与模式0和0密钥ID相同的认证格式. 没有
携带密钥版本:旋转是一个由主机控制的迁移,用旧适配器解密并
与同名编剧锁下的新适配器重写. 根据需要保留用于备份的旧密钥 .
将密钥保存在包含密码文本的任何备份/曝光边界外; 漏出两者
移除保护。 OS封口和主机密钥封口不打开对方的信封;切换
需要同样的刻意解密/重写迁移.
威胁模式
见 警卫。 。 。 .