@arkstack/encryption

中文文档
0.22.2发布于 2026/11/07ES Module零依赖近 30 天下载 4128

Isomorphic end-to-end encryption for Arkstack: AES-256-GCM ciphers, ECDH key pairs, and key generation/comparison utilities that run identically in Node and the browser.

原始使用文档
文档目录16 节
  1. Table of Contents
  2. Installation
  3. What's in the box
  4. Symmetric encryption
  5. Additional authenticated data
  6. Bytes
  7. Keys
  8. Comparing keys
  9. Fingerprints
  10. End-to-end encryption
  11. Secure channels
  12. Sealed boxes
  13. Verifying a conversation
  14. Synchronous Node API
  15. Wire formats
  16. Runtime requirements

@arkstack/encryption

@arkstack/encryption

A zero dependency isomorphic encryption for Arkstack. One implementation, built on the Web Crypto API, that runs unchanged in Node, Deno, Bun, browsers and workers, so anything encrypted on the server decrypts in the browser, and anything encrypted in the browser decrypts on the server.

Table of Contents

Installation

pnpm add @arkstack/encryption

Arkstack applications already have it through @arkstack/common re-exports in Encryption.

What's in the box

ExportWhat it does
CipherAES-256-GCM symmetric encryption
EncryptionKeyA symmetric key value: generate, derive, fingerprint, compare
KeysKey generation and constant time comparison helpers
KeyPairECDH P-256 identities, serialisable to base64url
SecureChannelA shared key between two identities, and messages over it
SealedBoxAnonymous encryption to a public key
Codecbase64url / hex / utf8 conversion and constant time compare
NodeCipherSynchronous AES-256-GCM for Node (@arkstack/encryption/node)

Symmetric encryption

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"

A cipher instance imports the key once, which is worth doing when encrypting many values:

const cipher = await Cipher.from(process.env.APP_KEY!)

const rows = await Promise.all(values.map((value) => cipher.encrypt(value)))

Any key representation works: an EncryptionKey, raw Uint8Array bytes, a CryptoKey, a base64url string of exactly 32 bytes, or any other string, which is hashed with SHA-256 and treated as a passphrase.

Additional authenticated data

aad is not encrypted, but it is bound to the ciphertext: decryption fails unless the same value is supplied. Use it to pin a payload to the context it belongs in, so a valid ciphertext cannot be replayed somewhere else.

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

Bytes

encryptBytes / decryptBytes take and return Uint8Array for binary payloads.

Keys

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)

Passwords get stretched, secrets get hashed:

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

Comparing keys

Every comparison here runs in constant time, and never throws on malformed input — it returns 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

Fingerprints

A fingerprint is a digest of a key, safe to display or log. Two people reading the same fingerprint are holding the same key.

await Keys.fingerprint(key)
// "3f8a1c02 9b4e7d15 c6a0ff31 2e5b8d94"

End-to-end encryption

An identity is an ECDH P-256 key pair. The public half is published, the private half never leaves its owner.

import { Keys } from '@arkstack/encryption'

const identity = await Keys.generateSerializedPair()
// { publicKey: 'MFkwEwYH…', privateKey: 'MIGHAgEA…' }

Both halves are base64url DER, so they survive JSON, headers, query strings and database columns unchanged, and import cleanly on the other runtime.

Secure channels

Each side combines its own private key with the other side's public key. Both arrive at the same AES-256-GCM key without it ever crossing the wire — the server can route the ciphertext without being able to read it.

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"

Pass info to derive separate keys for separate purposes from the same pair of identities:

const chat = await SecureChannel.between(alice.privateKey, bobPublicKey, {
  info: `chat:${id}`,
})
const files = await SecureChannel.between(alice.privateKey, bobPublicKey, {
  info: `files:${id}`,
})

Sealed boxes

Encrypt to a public key with no identity of your own. A throwaway key pair is generated per message and its public half travels in the payload; only the recipient's private key can open the result — the sender cannot decrypt their own message afterwards.

import { SealedBox } from '@arkstack/encryption'

const payload = await SealedBox.seal('anonymous tip', recipientPublicKey)

await SealedBox.open(payload, recipientPrivateKey) // "anonymous tip"

Verifying a conversation

Key agreement protects against eavesdroppers, not against a server that hands each side the wrong public key. A safety number lets the participants rule that out over any channel they already trust.

const number = await Keys.safetyNumber(alicePublicKey, bobPublicKey)
// "48213 90277 11408 63925 …"

await Keys.confirmSafetyNumber(alicePublicKey, bobPublicKey, scanned)

The value is identical on both sides regardless of who initiated, and whitespace is ignored when confirming. channel.safetyNumber() returns the same string for an open channel.

Synchronous Node API

The Web Crypto API is asynchronous everywhere. When a synchronous call is genuinely needed on the server, @arkstack/encryption/node provides one that emits byte-identical payloads:

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!))

It lives behind its own entry point so browser bundles never pull in node:crypto.

NodeCipher.resolve() treats a base64url string of exactly 32 bytes as raw key material and anything else as a passphrase, mirroring EncryptionKey.resolve(). When a secret must always be hashed — as APP_KEY is — use NodeCipher.fromSecret().

Wire formats

Both ciphers read and write the same payloads:

KindFormat
Cipher<iv>:<authTag>:<ciphertext>
Sealed boxark1:<ephemeralPublicKey>:<iv>:<authTag>:<ciphertext>

Every segment is unpadded base64url. The IV is 12 bytes, the GCM tag is 16 bytes.

Runtime requirements

A Web Crypto implementation on globalThis.crypto:

  • Node 19+, or Node 18 with globalThis.crypto available.
  • Browsers in a secure context (https or localhost).
  • Deno, Bun, and Cloudflare/Deno-style workers out of the box.

The synchronous /node entry point requires Node.

版本与发布记录

@arkstack/encryption 共发布 28 个版本,首次发布于 2026/08/27;过去 12 个月发布了 28 个版本。

版本发布日期距今
0.22.2latest2026/11/07今天
0.22.12026/11/052 天前
0.22.02026/11/052 天前
0.21.12026/11/052 天前
0.21.02026/10/2810 天前
0.20.22026/10/2315 天前

关于 @arkstack/encryption 的常见问题

@arkstack/encryption 是什么?

Isomorphic end-to-end encryption for Arkstack: AES-256-GCM ciphers, ECDH key pairs, and key generation/comparison utilities that run identically in Node and the browser.

如何安装 @arkstack/encryption?

在项目目录运行 npm install @arkstack/encryption,或使用 pnpm add / yarn add / bun add。

@arkstack/encryption 支持 TypeScript 吗?

该包的 package.json 没有声明类型入口。需要类型提示时,可以在 npm 上查找社区维护的 @types/arkstack__encryption 是否存在。

@arkstack/encryption 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@arkstack/encryption 可以商用吗?

@arkstack/encryption 使用 未声明 许可。package.json 没有声明许可证。没有许可证的代码默认保留全部权利,商用前请联系作者或查看仓库中的 LICENSE 文件。以上为概括,不构成法律意见,请以许可原文为准。

@arkstack/encryption 的下载量有多少?

根据 npm 官方下载统计,@arkstack/encryption 在 2026/10/07 – 2026/11/05 这 30 天内被下载 4,128 次,日均约 138 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@arkstack/encryption 还在维护吗?

最新版本 0.22.2 发布于 2026/11/07(今天);至今共发布 28 个版本,其中过去 12 个月发布了 28 个;首次发布于 2026/08/27。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。