@ byokit/ seal
手提箱,密箱和Ed25519签名.
Box, secretbox和Ed25519 节点22+,浏览器/PWAs和React Industrial的签名,没有本地模块,也没有
节点运行时间导入 。 对于存储或发送小秘密的应用程序, 需要字节相同的封装箱到处
(参见兼容向量的API表和测试).
安装
npm install @byokit/seal
快速启动
npm install @byokit/seal
在本地反应时安装 crypto.getRandomValues 使用前多填充( 例如
react-native-get-random-values) (中文(简体) ).
封住并打开一个盒子,将JSON值保存在密箱中,并签名和验证一个消息:
import {
boxKeyPair, boxPublicKey, boxKeyPairFromSeed, sealBox, openBox, openBoxFromSeed,
box, openAuthBox, sealNotice, openNotice,
sealJson, openJson,
signingKeyPairFromSeed, signDetached, verifyDetached,
} from '@byokit/seal';
const text = new TextEncoder().encode('hello from the phone');
// Box: anyone with the recipient's public key can seal; only the secret opens.
const recipientSeed = crypto.getRandomValues(new Uint8Array(32));
const recipient = boxKeyPairFromSeed(recipientSeed);
const bundle = sealBox(text, recipient.publicKey);
console.log('box bytes:', bundle.length);
console.log('opened:', new TextDecoder().decode(openBox(bundle, recipient.secretKey)!));
console.log('wrong key:', openBox(bundle, crypto.getRandomValues(new Uint8Array(32))));
// Secretbox JSON: one 32-byte key seals and opens data at rest.
const key = crypto.getRandomValues(new Uint8Array(32));
const stored = sealJson({ machines: ['desk'] }, key);
console.log('json:', openJson(stored, key));
stored[stored.length - 1] ^= 1;
console.log('tampered json:', openJson(stored, key));
// Ed25519 detached signatures.
const signing = signingKeyPairFromSeed(crypto.getRandomValues(new Uint8Array(32)));
const signature = signDetached(text, signing.secretKey);
console.log('signature bytes:', signature.length, 'secret key bytes:', signing.secretKey.length);
console.log('verified:', verifyDetached(text, signature, signing.publicKey));
console.log('other message:', verifyDetached(new TextEncoder().encode('changed'), signature, signing.publicKey));
运行 TypeScript 文件的输出 node (节点22.18+):
box bytes: 92
opened: hello from the phone
wrong key: null
json: { machines: [ 'desk' ] }
tampered json: null
signature bytes: 64 secret key bytes: 64
verified: true
other message: false
92字节盒为32(ephemeral public key)+24(nonce)+16(MAC)+20(信息).
API 一览
| 导出 | 它会做什么 |
|---|---|
boxKeyPair(rng?) | 新鲜出品的32字节X25519秘密及其公用钥匙 |
boxPublicKey(secret) | X25519 原始32字节密钥 |
boxKeyPairFromSeed(seed) | X25519 从32字节种子取出;与libsodium相同 crypto_box_seed_keypair |
sealBox(bytes, recipientPublicKey, rng?) | 将字节刻入有新电子键的公钥 |
openBox(bundle, recipientSecret) | 打开一个原始的秘密; Uint8Array 或者说 null |
openBoxFromSeed(bundle, recipientSeed) | 以种子打开, 如原始 API |
box(bytes, theirPublic, mySecret, rng?) | 已知的认证框 |
openAuthBox(bundle, theirPublic, mySecret) | 打开已知的发送框; Uint8Array 或者说 null |
sealNotice(value, boxPublicKey, rng?) | JSON在匿名盒子里; { v: 1, sealed: string } |
openNotice(data, secret) | 以原始秘密打开通知; unknown 或者说 null |
sealSecretBox(bytes, key, rng?) | 带有 32 字节密钥的密封字节 |
openSecretBox(bundle, key) | 打开密箱; Uint8Array 或者说 null |
sealJson(value, key, rng?) | JSON.stringify, UTF-8, 然后是密箱 |
openJson(bundle, key) | 打开和剖析; 值或 null |
signingKeyPairFromSeed(seed) | 来自32字节种子的 Ed25519 密钥对; 微博nacl-style 64-字节密钥 |
signDetached(bytes, secretKey) | 64字节脱节签名 |
verifyDetached(bytes, signature, publicKey) | true 或者说 false |
RandomBytes (类型) | (length: number) => Uint8Array,封条函数的可选 RNG |
每个电话在一个地方:
import {
boxKeyPair, boxPublicKey, boxKeyPairFromSeed, sealBox, openBox, openBoxFromSeed,
box, openAuthBox, sealNotice, openNotice,
sealSecretBox, openSecretBox, sealJson, openJson,
signingKeyPairFromSeed, signDetached, verifyDetached,
} from '@byokit/seal';
const recipient = boxKeyPairFromSeed(recipientSeed); // 32-byte seed; same as libsodium crypto_box_seed_keypair
const bundle = sealBox(plaintext, recipient.publicKey);
const opened = openBox(bundle, recipient.secretKey); // Uint8Array | null
const sealed = sealSecretBox(plaintext, secretboxKey); // 32-byte key
const unsealed = openSecretBox(sealed, secretboxKey); // Uint8Array | null
const stored = sealJson({ machines: [] }, secretboxKey);
const catalog = openJson(stored, secretboxKey); // unknown | null
const signing = signingKeyPairFromSeed(signingSeed); // 32-byte seed
const signature = signDetached(plaintext, signing.secretKey); // 64-byte signature
verifyDetached(plaintext, signature, signing.publicKey); // boolean
原始密钥、经认证的盒子和通知
const sender = boxKeyPair();
const device = boxKeyPair();
const authenticated = box(text, device.publicKey, sender.secretKey);
const opened = openAuthBox(authenticated, sender.publicKey, device.secretKey);
const notice = sealNotice({ kind: 'completed', job: 'build' }, device.publicKey);
const event = openNotice(notice, device.secretKey);
openBox 现在拿一个生X25519秘密。 对于所储存的种子,保留 boxKeyPairFromSeed(seed) 并通过它的
secretKey,或替换旧的 openBox(bundle, seed) 调用 openBoxFromSeed(bundle, seed)。 。 。 .
所储存的密码和种子衍生物不变。 一个原始的秘密和一个种子是不同的投入.
已知的发报框需要端点信任的公钥;两个端点都可以生成其密码文本,所以是
非签名 。 匿名信箱和通知无法识别发送者。 验证解密的通知值
在他们面前行动之前的应用 贾森 null 无法区分打开失败; 未定义的值,
循环对象和BigInts不能被封为JSON.
在 iOS 通知服务扩展中打开通知
没有JavaScript在iOS通知服务扩展中运行,因此包还装有Swift开关器,
ios/Sources/ByokitSeal/ByokitSeal.swift (只有CryptoKit,iOS 13+). 将该文件添加到扩展目标中 。 与
@byokit/relay是因为 mutableContent: true,iOS在提醒显示前运行扩展,即使应用程序不是
运行中,扩展可以以通知取代通用文本 :
import UserNotifications
final class NotificationService: UNNotificationServiceExtension {
override func didReceive(_ request: UNNotificationRequest, withContentHandler done: @escaping (UNNotificationContent) -> Void) {
let content = request.content.mutableCopy() as! UNMutableNotificationContent
if let secret = ByokitSeal.keychainSecret(service: "app:no-auth", account: "notice-secret", accessGroup: "TEAMID.com.example.shared"),
let notice = ByokitSeal.openNotice(userInfo: request.content.userInfo, secret: secret) as? [String: Any],
let title = notice["title"] as? String, let body = notice["body"] as? String {
content.title = title // validate before showing; the notice's sender is not authenticated
content.body = body
}
done(content) // on any failure the generic alert shows unchanged
}
}
应用程序拥有通知密钥: 它使用 boxKeyPair(),将公钥给主机,只保存
32字节秘密作为密钥链访问组中未添加的碱基64url字符串,与其扩展共享,可在
第一个解锁( 带有 expo- security- store :) setItemAsync('notice-secret', secret, { accessGroup, keychainAccessible: AFTER_FIRST_UNLOCK }),哪些存储服务 app:no-auth) (中文(简体) ). keychainSecret 完全读取此项目; 扩展
从不读取其他密钥链项,也从不登录该密钥或通知. openNotice(envelope, secret:) 打开一个
信封来自任何地方。 它与 TypeScript 打开器的字节对等由检查
ios/Tests/ByokitSealTests 针对矢量 此包的测试 制取并打开 libsodium (swift test 在此
目录). 一个区别是无法避免的: Swift 字符串不能持有一个未发售的 UTF-16 代孕,所以通知
其 JSON 包含一个(例如文本切入 slice 通过emoji)在TypeScript中打开,但在斯威夫特为零,
和一般警报显示。 密封成文的文字(String.prototype.toWellFormed) (中文(简体) ).
格式
sealBoxx25519公用钥匙的发售(32个)crypto_box_easy密码文本 (16字节MAC先行).box(24个)crypto_box_easy密码文本 (16字节MAC先行).sealNotice发射{ v: 1, sealed }时sealed未添加基数64url =sealBoxUTF-8 JSON 字节.sealSecretBox(24个)crypto_secretbox_easy密码文.sealJson用途JSON.stringify然后是UTF-8;openJson解析 UTF-8并返回null失败.openBox, (中文(简体) ),openAuthBox财务报告和审定财务报表openSecretBox返回时null短,被篡改,或错误的钥匙捆.- 无效的密钥长度传递给封存/ 签名抛出 .
随机和密钥
每个印章接受一个可选的最终 (length: number) => Uint8Array 用于确定性试验的RNG
默认为 crypto.getRandomValues。 。 。 .
import { sealSecretBox, type RandomBytes } from '@byokit/seal';
// Tests only: a fixed RNG makes the output repeatable. Never in production.
const fixed: RandomBytes = (length) => new Uint8Array(length).fill(7);
const key = new Uint8Array(32).fill(1);
const a = sealSecretBox(new Uint8Array([1, 2, 3]), key, fixed);
在生产中绝不再重复使用带有相同钥匙或可替代确定性RNG的密箱。 保守秘密 隐藏秘密 平台安全存储; 此库不管理存储或密钥.
依赖关系
固定用途 @noble/curves, (中文(简体) ), @noble/ciphers 财务报告和审定财务报表 @noble/hashes。没有本地模块或节点运行时间导入.
测试
套房检查固定的 libsodium/tweetnacl 向量和与互操作性的双向 sodium-native, (中文(简体) ),
包括篡改和错误的密钥案件。 运行 npm run build && npm run check && npm test 在寄存器根处.
链接
- 字节,存储器根
- 变动情况
- 警卫
examples/expo: iOS和Android上的盒子、密箱和签名,带有crypto.getRandomValues多元填充( e)expo-crypto(单位:千美元)polyfills.ts