@arkstack/encryption
Arkstack 的零依赖异构加密 。 一个基于Web Crypto API的执行,在Node,Deno, Bun,浏览器和工人中运行不变,所以 在服务器上加密的浏览器解密, 在服务器上加密的浏览器解密。 。 。 .
目录
安装
pnpm add @arkstack/encryption
Arkstack 应用程序已经通过 @arkstack/common 再出口情况 Encryption。 。 。 .
盒子里的东西
| 导出 | 它会做什么 |
|---|---|
Cipher | AES-256-GCM 对称加密 |
EncryptionKey | 对称的密钥值:生成、出自、指纹、比较 |
Keys | 密钥生成和恒定时间比较助手 |
KeyPair | ECDH P-256 身份,可序列至基数64url |
SecureChannel | 两个身份之间共享的密钥和上面的信息 |
SealedBox | 匿名加密公钥 |
Codec | base64url / hex / utf8 转换和恒定时间比较 |
NodeCipher | 节点同步 AES-256-GCM(@arkstack/encryption/node(中文(简体) ) |
对称加密
import { Cipher, Keys } from '@arkstack/encryption'
const key = Keys.generate()
const payload = await Cipher.encrypt('my-secret-value', key)
// "abc123:def456:ghi789"
await Cipher.decrypt(payload, key)
// "my-secret-value"
密码实例导入密钥一次, 当加密许多值时值得这样做 :
const cipher = await Cipher.from(process.env.APP_KEY!)
const rows = await Promise.all(values.map((value) => cipher.encrypt(value)))
任何关键代表工作: EncryptionKey粗线 Uint8Array 字节, a CryptoKey,一个完全32字节的基质64url字符串,或任何其他字符串,用SHA-256来散开并当作密码句处理.
其他经认证的数据
aad 未加密,但它与密码文本相接:除非提供相同值,否则解密失败。 用它来将一个有效载荷固定在它所属的上下文上,所以一个有效的密码文本不能在其他地方重放.
const payload = await cipher.encrypt(body, { aad: `conversation:${id}` })
await cipher.decrypt(payload, { aad: `conversation:${id}` }) // ok
await cipher.decrypt(payload, { aad: 'conversation:other' }) // throws
字节
encryptBytes 编号 : decryptBytes 带回 Uint8Array 二进制有效载荷.
密钥
import { Keys } from '@arkstack/encryption'
Keys.generate() // EncryptionKey, 32 random bytes
Keys.generateString() // the same, base64url encoded for storage
Keys.token(16) // a random URL-safe token (not a key)
密码被拉伸,秘密被散开:
const { key, salt, iterations } = await Keys.derive(password) // PBKDF2-HMAC-SHA256
const same = await Keys.derive(password, { salt, iterations })
await Keys.fromSecret(process.env.APP_KEY!) // SHA-256, matching Arkstack's app key handling
比较密钥
这里的每个比较都会在恒定的时间运行, 并且从不丢入错误的输入—— 它返回 false。 。 。 .
Keys.compare(left, right) // two keys already in key form
await Keys.matches(passphrase, key) // resolves both sides first
await Keys.samePublicKey(left, right) // two identities
指纹
指纹是钥匙的文摘,可以安全显示或日志. 两个人读取相同的指纹 拿着相同的钥匙.
await Keys.fingerprint(key)
// "3f8a1c02 9b4e7d15 c6a0ff31 2e5b8d94"
端到端加密
一种身份是ECDH P-256键对. 公开的半个出版,私人的半个不离开主人.
import { Keys } from '@arkstack/encryption'
const identity = await Keys.generateSerializedPair()
// { publicKey: 'MFkwEwYH…', privateKey: 'MIGHAgEA…' }
两者都为base64url DER,因此在JSON,信头,查询字符串和数据库列不变的情况下幸存了下来,并在其他运行时间被干净地导入.
安全频道
每方将自己的私钥与对方的公钥结合. 两者都到达了相同的AES-256-GCM密钥而从未穿过过电线——服务器可以在无法读取的情况下去路由密码文本.
import { SecureChannel } from '@arkstack/encryption'
// In the browser, as Alice
const outbound = await SecureChannel.between(alice.privateKey, bobPublicKey)
const message = await outbound.encrypt('hey bob')
// On Bob's device
const inbound = await SecureChannel.between(bob.privateKey, alicePublicKey)
await inbound.decrypt(message) // "hey bob"
过 info 从同一对身份中获取用于不同目的的单独密钥:
const chat = await SecureChannel.between(alice.privateKey, bobPublicKey, {
info: `chat:${id}`,
})
const files = await SecureChannel.between(alice.privateKey, bobPublicKey, {
info: `files:${id}`,
})
密封盒
加密到没有自己身份的公钥 。 每条消息都会生成一对被丢弃的密钥,其公用半部在有效载荷中出行;只有接收者的私人密钥才能打开结果——发送者之后无法解密自己的消息.
import { SealedBox } from '@arkstack/encryption'
const payload = await SealedBox.seal('anonymous tip', recipientPublicKey)
await SealedBox.open(payload, recipientPrivateKey) // "anonymous tip"
正在验证对话
密钥协议可以防止偷听者,而不是防止服务器将错误的公钥相接. 一个安全号码让参与者可以排除他们已经信任的任何频道.
const number = await Keys.safetyNumber(alicePublicKey, bobPublicKey)
// "48213 90277 11408 63925 …"
await Keys.confirmSafetyNumber(alicePublicKey, bobPublicKey, scanned)
无论由谁发起,双方的值都是相同的,确认时会忽略白空. channel.safetyNumber() 返回打开的频道的相同字符串.
同步节点 API
Web Crypto API是各地同步的. 当服务器上真的需要同步呼叫时, @arkstack/encryption/node 提供一种发射字节相同的有效载荷:
import { NodeCipher } from '@arkstack/encryption/node'
const payload = NodeCipher.encrypt('value', NodeCipher.fromSecret(process.env.APP_KEY!))
NodeCipher.decrypt(payload, NodeCipher.fromSecret(process.env.APP_KEY!))
它住在自己的入口后 所以浏览器捆绑永远不会拉进来 node:crypto。 。 。 .
NodeCipher.resolve()将一个完全32字节的基质64url字符串作为原始密钥材料,其他东西作为密码句,镜像EncryptionKey.resolve()当一个秘密必须永远被散开时——如APP_KEY已经--使用NodeCipher.fromSecret()。 。 。 .
线条格式
两个密码读取和写出相同的有效载荷:
| 仁爱 | 格式 |
|---|---|
| 密码 | <iv>:<authTag>:<ciphertext> |
| 密封盒 | ark1:<ephemeralPublicKey>:<iv>:<authTag>:<ciphertext> |
每段都是未加入基数64url. IV为12字节,GCM标记为16字节.
运行时间要求
网络加密执行 globalThis.crypto数字 :
- 节点 19+,或节点18与
globalThis.crypto可用. - 浏览器 在安全的情况下
https或者说localhost) (中文(简体) ). - 德诺, (中文(简体) ), 阿宝和Cloudflare/德诺风格的工人 走出盒子.
同步 /node 入口需要节点.