# @claudewerk/mutual-key-auth 中文文档

规范地址：https://npmcn.com/package/%40claudewerk/mutual-key-auth/

版本：0\.2\.1
npm 元数据更新时间：2026-10-27T10:00:42.504Z
中文译文更新时间：2026-11-10T00:18:08.217Z
软件包许可：MIT

本文由上游 README 翻译，可能存在偏差，请结合原文核对 API、命令与配置。版权与许可归原作者所有。

原始 npm 页面：https://www.npmjs.com/package/%40claudewerk/mutual-key-auth
原文对照：https://npmcn.com/package/%40claudewerk/mutual-key-auth/?lang=original
源码仓库：https://github.com/GATE-software-space/claudewerk-packages

## 关键信息（来自 npm 元数据）

- 包体积（解压后）：88 kB，25 个文件
- 模块格式：ES Module
- TypeScript 类型：包内自带
- 运行依赖：0 个
- peer 依赖：pino \*
- 首次发布：2026-09-28T08:43:27.167Z
- 最新版本发布：2026-10-27T10:00:42.120Z
- 近 30 天下载量：1910（2026-10-10 至 2026-11-08，npm 官方下载统计）
- 已发布版本数：3（近 12 个月 3 个）

## 中文简介

互换,独立可撤销的无记名钥匙认证,并附有质疑响应方向证明和下级约束.

## 安装

```sh
npm install @claudewerk/mutual-key-auth
```

也可以使用：`pnpm add @claudewerk/mutual-key-auth`、`yarn add @claudewerk/mutual-key-auth`、`bun add @claudewerk/mutual-key-auth`

## README

# 双键原子

相互、可独立撤销 **无记名钥匙认证** 与 **挑战反应方向证明** 财务报告和审定财务报表 **下级绑定**独立和 无依赖 - `node:crypto` 仅Bun WebCrypto, `pino` 一个可选的对等点.

它对WebSockets,队列或任何更大的系统一无所知. 它薄荷和 验证密钥,制作并检查方向证明. 传送器插入 作为 `AuthProvider` (duck-typed -- 两种方式都不导入).

```bash
bun add @claudewerk/mutual-key-auth      # or: npm i @claudewerk/mutual-key-auth
bun test                      # 58 tests, all attack cases included
```

## 为什么存在(威胁模式)

连接上的两个同伴 都想不断独立地证明, 对方仍然持有有效的证书 -- 没有人降低 谈判参数 中手摇。 每个机制都为特定攻击辩护:

| 机制 | 防御 |
| --- | --- |
| 存储的密钥 **散开** (SHA-256),一次显示原始 | DB/唱片被窃并不能揭示出原始的无记名密钥(没有预想). 它不会停止握手作假:所储存的散列是防向的HMAC密钥,因此被窃记录可以冒充同位素. 警卫记录,如私钥;固定为不对称证明(RFC 0004§9.1/§10) |
| **常时** hash + 证明比较 | 泄露秘密字节的时空侧道 |
| **过期** 每个键 | 泄露的密钥仅在到期前有用 |
| **独立撤销** (按键,任意一边) | 折中一个对等方的密钥不会迫使另一个完全重键 |
| **按键旋转** (两个活键重叠) | 在不硬切换/ 丢弃连接的情况下滚动密钥 |
| 加拿大 **单用途非ce** | 重放被捕获的证明(nonce为一发;被烧毁的nonce不能被再用) |
| 诺斯 **计时器** | 无限挑战有效性; 缩小重播窗口 |
| **方向证明** =: `HMAC(transcript, keySecret)` | 证明拥有 _不把钥匙放电线上_; 将证据绑定在一个证明角色上 |
| **角色绝对** 记录`initiatorHello`\+ 键`responderHello` 固定顺序, `proverRole` 页:1 | 两端都用零视角杂耍来计算字节-同声记录 - 没有我的/同声互换的脚枪 |
| 缩写绑定 **两个相框的花序 + 谈判的跨层拖轮** `{version, features, and every negotiation slot's chosen name}` | **每层都有降级攻击** - 篡改任何谈判参数(运输) _或者说_ app-level:编码,功能集,认证方法)更改了笔录,因此证明不再被验证(SSH exchange-hash / TLS-Fined property) |
| **`transcriptHash` 绑定** (用两个原始的广告打招呼框) | MITM在谈判前根据公布的能力进行编辑 -- -- 认证全文,而不仅仅是谈判结果 |
| **`connId` 绑定** | 交叉连接重放抓取的证据到不同的连接 |
| **长度**,固定数量记录 | 凝聚-凝聚攻击+ 添加/移动-场 篡改地界 |
| **域分隔标签** + 记录中的格式标签 | 重新使用证据 HMAC 作为通用签名预告 |
| **范围 - > 动词** 矩阵检查 | 过于宽泛的授权;一个关键只是它所赋予的范围 |
| **编辑日志** | 意外地记录原始密钥、散列或证据 |
| **`ed25519-sig` 方法**:记录持有公用钥匙,证据是同一笔录上的Ed25519签名(RFC0004§10) | 校验-存储盗窃:被窃记录验证了证据但不能制作一个,所以不能冒充对等者-关闭下方的HMAC取舍 |
| **签名配对票** (单位:千美元)`brgt1_...`, Ed25519,载在标有标记的罐装有效载荷上,过期,为4KiB盖) | URL 或 公钥在主机之间复制票时被交换 -- -- 编辑后卡失败 `bad-signature` |
| **对格式错误的输入进行失败的闭合握手** (非字符串 / 过大的非ce 或 证明 - > `malformed-auth-message`; 加密层本身的拒绝被捕获) | 远程相撞:在载体的认证交换中抛出一个会终止这一过程,所以一个可以制造的对等点 `prove()` 用一个短的非ce来扔, 在认证前会杀死一个听众 |

### 已知取舍( 读此)

证据是 **对称的 HMAC** 由密钥在后端的密钥键入 (单位:千美元)`SHA-256(rawKey)` == `record.hash`) (中文(简体) ). 证明者从它的生钥匙中得来 核查者已将其记录在案; **原始密钥从不移动**编辑 权衡:偷取所存储记录的攻击者可以 _伪造方向证明_ (虽然没有通过生钥匙核查 -- -- 这仍然需要一个预想). 这是 内在的对称 HMAC 有散列存储。 缓解:短期限

-   快速独立撤销,两者都建在. 如果你需要证据才能活下来 恢复妥协,使用 **`ed25519-sig`** 方法(下一节):相同 握手,同样的下级绑定,记录只持有公用钥匙.

## 使用量

### 签字方法`ed25519-sig`) -- 更喜欢这个你可以

```js
import { mintSigningKey, recordForPublicKey, publicKeyOf, asSigAuthProvider } from "@claudewerk/mutual-key-auth";

const me = mintSigningKey({ peerId: "hostA" });   // { secretKey, record }
// ship me.record (or just publicKeyOf(me.secretKey)) to the peer; it holds no secret
const peerRecord = recordForPublicKey(peerPublicKey, { peerId: "hostB", scopes: ["send"] });

const provider = asSigAuthProvider({ mySecretKey: me.secretKey, peerRecord }); // method "ed25519-sig"
```

`secretKey` 实值 `brg-ed25519_<base64url 32-byte seed>`: 储存像任何 私钥. 握手、发出信息以及拒绝的原因是: `asAuthProvider`;只有原始证据不同. 一个听众从 `mutual-key` 两种方法同时提供(bun-liable-ws) `authByMethod`) (中文(简体) ).

### 配对入场券——每对对等者可复制一个字符串

```js
import { mintTicket, parseTicket } from "@claudewerk/mutual-key-auth";

// host B prints its card; host A pastes it
const card = mintTicket(B.secretKey, { peerId: "hostB", urls: ["wss://b.example/bridge"], expiresAt });
const got = parseTicket(card, { scopes: ["send"] }); // { ok, ticket: { peerId, publicKey, urls, ... }, record }
// got.record is A's key record for B; got.ticket.urls is where to dial
```

票是自签的联络卡: `peerId`,Ed25519 公用钥匙,拨打URL, 发行时间和可选过期。 签名在中途捕获任何编辑 不证明密钥属于你认为的人 -- -- 信任是频道 你收到它过来。 配对是一张票 从来没有秘密 移动。 范围是VERIFIER的设定(`parseTicket(..., { scopes })`从不 一张卡片上写的 拒绝: `malformed`, (中文(简体) ), `unsupported-version`, (中文(简体) ), `bad-signature`, (中文(简体) ), `expired`。 。 。 .

### 薄荷, 储存散列, 校验

```js
import { createAuthProvider } from "@claudewerk/mutual-key-auth";

const auth = createAuthProvider({ logger: "silent", prefix: "brg" });

// Listener mints a key for a peer. Show `key` ONCE; persist only `record`.
const { key, record } = auth.mintKey({
  peerId: "dialer-1",
  scopes: ["read", "write"],
  expiresAt: Date.now() + 86_400_000, // 24h
});
// key    -> "brg_Xf9...b64url..."   (give to the peer, never stored/logged)
// record -> { id, peerId, scopes, expiresAt, status:"active", hash, ... }

// Later, verify a presented key against the stored record:
const r = auth.verifyKey(presentedKey, record);
// { ok: true, peerId, keyId } | { ok: false, reason: "expired"|"revoked"|"key_mismatch"|... }
```

### 相互握手(`asAuthProvider` ——接通可靠接通 lib)

`asAuthProvider` 符合 **没错** 页:1 `AuthProvider` 约一个可靠的 - WebSocket 库( R)[bun-liable-ws (可信赖的bun-liable-ws) (英语)](https://github.com/)\-形状)驱动器 - 不 适配器,无胶. 它回来了 `{ method, start(ctx), step(ctx, incoming) }` 财务报告和审定财务报表 运行三个消息的相互交换 在证明原始。 把它作为电线 工厂`auth: () => asAuthProvider({...})`)——每次接通一次握手.

```js
import { asAuthProvider } from "@claudewerk/mutual-key-auth";

// Mutual keys: each side holds its OWN raw key; each holds the OTHER's record.
const dialer   = asAuthProvider({ myKey: myRawKey,  peerRecord: peerRecordForListener });
const listener = asAuthProvider({ myKey: myRawKey2, peerRecord: peerRecordForDialer });
```

连接库各递 `start`编号 :`step` 调用 a **新鲜** `AuthContext` (它每次重建它),所以 **所有握手状态 住在提供者 关闭, 永远不要在 ctx 上**。你什么也得不到 -- -- 图书馆提供:

```
AuthContext = { role: "dialer"|"listener", negotiated, connId,
                localCapabilities, remoteCapabilities, transcriptHash, localPeerId }
AuthStep    = { done:false, send } | { done:true, ok:true, send? }
            | { done:true, ok:false, reason, send? }
```

```js
// The library drives it -- shown here manually with a fresh ctx per call:
const d0 = dialer.start(dialerCtx());        // -> { done:false, send:{type:"challenge"} }
listener.start(listenerCtx());               // -> null (listener waits)
const l1 = listener.step(listenerCtx(), d0.send);   // -> challenge-response
const d1 = dialer.step(dialerCtx(),   l1.send);     // -> { done:true, ok:true, send:response }
const l2 = listener.step(listenerCtx(), d1.send);   // -> { done:true, ok:true }
// d1.ok && l2.ok === mutual success. A tampered negotiated/connId/transcriptHash/
// capability aborts it with { ok:false, reason:"bad-proof" }.
```

每个角色的证明都具有约束力: `H(initiatorHello)` + 键 `H(responderHello)` (从) `local`编号 :`remoteCapabilities`页:1 `negotiated` tuple(版本、特征和 每个谈判时间段的选定名称, `connId`,以及 `transcriptHash` - 所以任何跨层降级或MITM 能力编辑断开握手.

### 方向验证原始( 下等, 下等)

握手就是建立在这个基础上的. 这个 **核查者** 签发非ce **验证** 返回共享上的 HMAC **角色绝对** 记录 两端都通过 相同的元素——没有视角杂耍.

```js
import { PROVER_ROLE } from "@claudewerk/mutual-key-auth";

const parts = {
  proverRole: PROVER_ROLE.INITIATOR,     // enum: who is proving (not a direction string)
  initiatorHello,                        // FIXED order: initiator first,
  responderHello,                        //   then responder -- same on both ends
  tuple: { version: 1, features: ["ordered"], auth: "mutual-key", e2eSeal: "none", e2eSign: "none", codec: "json" },
  connId,                                // binds connection identity (optional)
  transcriptHash,                        // binds the full advertised-capability transcript (optional)
};

const nonce = auth.newNonce();                                      // verifier issues
const proof = auth.makeProof(proverRawKey, { nonce, transcriptParts: parts }); // prover (key never leaves)
const ok = auth.verifyProof(proof, {                               // verifier: consumes nonce, checks
  nonce, transcriptParts: parts, keyRecordForProver: proverRecord //   revocation/expiry, then HMAC
});
// ok === true only if the key is valid AND no bound parameter (any layer) was tampered
```

> **一个非ce=一次尝试.** `verifyProof` 在加密前消耗非ce 检查,所以一个失败的证据会烧掉它 -- 用新鲜的来重试挑战 `newNonce()`,从不重发相同的非ce。 (悬挂在 `DESIGN-FEEDBACK.md #3`. ) (中文(简体) )

### 范围 - > 动词检查( 纯)

```js
import { scopeAllows } from "@claudewerk/mutual-key-auth";

const matrix = { read: ["get", "list"], write: ["put", "delete"], admin: ["*"] };
scopeAllows(["read"], "get", matrix);    // true
scopeAllows(["read"], "delete", matrix); // false
scopeAllows(["admin"], "anything", matrix); // true  ("*" grants all)
```

### 撤销旋转( R)

```js
const revoked = auth.revokeKey(record); // returns a revoked copy; verify* now rejects it
// Rotation: mint a second key before expiring the first -- both verify while active.
```

## API 密码

```
// The handshake AuthProvider a reliable-connection lib drives (challenge/response):
asAuthProvider({ crypto?, myKey, peerRecord, method?, domainSepLabel?, now? })
  -> { method, start(ctx) -> AuthStep|null, step(ctx, incoming) -> AuthStep }
  ctx      = { role: "dialer"|"listener", negotiated, connId,
               localCapabilities, remoteCapabilities, transcriptHash }  // READ-ONLY, rebuilt each call
  AuthStep = { done:false, send } | { done:true, ok:true, send? }
           | { done:true, ok:false, reason, send? }
  // State lives in the closure, NOT on ctx -- create one provider per connection.

// The same handshake with Ed25519 signature proofs (RFC 0004 §10):
asSigAuthProvider({ mySecretKey, peerRecord, method?, domainSepLabel?, now? })  // method "ed25519-sig"
mintSigningKey({ peerId, scopes?, expiresAt?, prefix?, rateLimit?, allowlist? }) -> { secretKey, record }
recordForPublicKey(publicKey, { peerId, scopes?, expiresAt?, ... }) -> record
publicKeyOf(secretKey) -> string   // base64url raw public key
makeSigProof(secretKey, { nonce, transcriptParts }) -> string
verifySigProof(proof, { nonce, transcriptParts, keyRecordForProver }) -> boolean

// Pairing tickets (self-signed contact cards for ed25519-sig peers):
mintTicket(secretKey, { peerId, urls, expiresAt?, now? }) -> "brgt1_..."
parseTicket(text, { scopes?, now? }) -> { ok: true, ticket, record } | { ok: false, reason }

// The stateful key/nonce/proof provider (crypto operations, not a state machine):
createAuthProvider({ logger?, prefix?, nonceTtlMs?, nonceBytes?, now? }) -> {
  mintKey({ peerId, scopes?, expiresAt?, prefix?, rateLimit?, allowlist? }) -> { key, record }
  // recordForKey(key, { peerId, ... }) -> record   (top-level export; for a key you already hold)
  verifyKey(presented, record) -> { ok, reason?, peerId?, keyId? }
  newNonce() -> string
  makeProof(myIssuedRawKey, { nonce, transcriptParts }) -> string
  verifyProof(proof, { nonce, transcriptParts, keyRecordForProver }) -> boolean
  revokeKey(record) -> record
  scopeAllows(scopes, verb, matrix) -> boolean
}

// transcriptParts (role-absolute; tuple is any canonicalizable object):
{ proverRole: PROVER_ROLE.INITIATOR|RESPONDER, initiatorHello, responderHello,
  tuple,              // e.g. WS Negotiated { version, features, auth, e2eSeal, e2eSign, codec }
  connId?, transcriptHash?, domainSepLabel? }
```

纯函数也直接导出( N)`asAuthProvider`, (中文(简体) ), `mintKey`, (中文(简体) ), `recordForKey`, (中文(简体) ), `deriveKey`, (中文(简体) ), `verifyKey`, (中文(简体) ), `makeProof`, (中文(简体) ), `verifyProof`, (中文(简体) ), `buildTranscript`, (中文(简体) ), `mintSigningKey`, (中文(简体) ), `recordForPublicKey`, (中文(简体) ), `publicKeyOf`, (中文(简体) ), `makeSigProof`, (中文(简体) ), `verifySigProof`, (中文(简体) ), `asSigAuthProvider`, (中文(简体) ), `SIG_METHOD`, (中文(简体) ), `mintTicket`, (中文(简体) ), `parseTicket`, (中文(简体) ), `PROVER_ROLE`, (中文(简体) ), `newNonce`, (中文(简体) ), `NonceStore`, (中文(简体) ), `scopeAllows`, (中文(简体) ), `createLogger`, (中文(简体) ), `redact`)用而无国供.

### 带外提供密钥

`mintKey` 生成一个密钥,并手拿回记录。 当钥匙 已经存在 - 它来自一个配置文件, 一个秘密商店, 或者一个固定的二 进程共享 -- -- 从中获取记录:

```js
import { recordForKey } from "@claudewerk/mutual-key-auth";

const peerRecord = recordForKey(process.env.PEER_KEY, { peerId: "rust-client" });
```

`mintKey` 实值 `generate + recordForKey`,因此两者生成相同的记录用于 同样的钥匙。 这就是让一个TS主机和一个单独的客户端程序达成一致的原因 对方的记录没有相互传递记录: 启动时对等方的生密钥,获得记录,只保留散列.

### 衍生(委托)密钥

一个长寿的父键可以支持许多短寿的同龄人,没有 薄荷服务,没有密钥存储. 父母的持有者每个子女 把它交给它产生的过程; 核查的一面,它也是 持有父母,从同龄人声称的科目中重新选定同一个孩子:

```js
import { deriveKey, recordForKey } from "@claudewerk/mutual-key-auth";

// spawner: one child key per conversation, passed in the child's env
const childKey = deriveKey(parentKey, conversationId);

// verifier: pick the record by the peer id claimed in the hello
const peerRecordFor = (peerId) =>
  peerId.startsWith("host/") ? recordForKey(deriveKey(parentKey, peerId.slice(5)), { peerId }) : undefined;
```

`deriveKey` 实值 `HMAC-SHA256(parent, "mutual-key-auth/derive/v1" || 0x00 || subject)` 作为 `<prefix>_<base64url>`儿童不透露其父母或 (a) 父母的兄弟姐妹; 谁拿着 因此,父母是家庭的秘密.

### `KeyRecord`

```
// mutual-key
{ id, peerId, scopes[], rateLimit?, allowlist?, expiresAt, status: "active"|"revoked",
  hash, prefix, createdAt }
// ed25519-sig: no secret at all
{ id, peerId, scopes[], rateLimit?, allowlist?, expiresAt, status: "active"|"revoked",
  alg: "ed25519", publicKey, createdAt }
```

`id` 是一个短而安全的到日志的手柄(散列的前12个六分轴). `hash` 这是 满 `SHA-256(rawKey)` - 在休息时视之为敏感(见上文权衡)。 这个 显示原始密钥 **仅限** 内 `mintKey`返回,从未坚持或记录.

## 日志

可注入、结构化和 **编辑**。通过 `logger`数字 : `"silent"` (违约), `"console"`, (中文(简体) ), `"pino"` (懒惰的可选对等点 -- -- 仅需要) 或任何鸭子型的 `{ info, warn, error, debug }` 对象。 每个 后端是包着的 `key`编号 :`secret`编号 :`proof`编号 :`token` 被删除 `hash` 实值 被蒙到前缀上——一个生键即使呼叫者通过一个也不能到达日志.

## 安全保证和非目标

-   **仅限CSPRNG** - 说吧 `node:crypto` `randomBytes` 编号 : `getRandomValues`。测试 声称没有源文件调用 `Math.random(`。 。 。 .
-   **常时** 比较关键和证据核查.
-   HMAC的证据是 **单一用途** -- 域分隔和格式标记, 不 一个通用签名神谕。 不得再用关键材料作别用.
-   **没有** 运输、会话管理或费率限制(`rateLimit`编号 :`allowlist` 被记录在记录上,供打电话者强制执行. **没有** 不对称 -- 来看权衡和 `DESIGN-FEEDBACK.md`。 。 。 .

## 许可证

麻省理工学院.

## 常见问题

### @claudewerk/mutual-key-auth 是什么？

互换,独立可撤销的无记名钥匙认证,并附有质疑响应方向证明和下级约束\.

### 如何安装 @claudewerk/mutual-key-auth？

在项目目录运行 npm install @claudewerk/mutual-key-auth，或使用 pnpm add / yarn add / bun add。

### @claudewerk/mutual-key-auth 支持 TypeScript 吗？

支持。包内自带 TypeScript 类型声明（package\.json 声明了 types 入口），安装后即可获得类型提示，无需再安装 @types 包。

### @claudewerk/mutual-key-auth 支持 ESM 还是 CommonJS？

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

### @claudewerk/mutual-key-auth 可以商用吗？

@claudewerk/mutual-key-auth 使用 MIT 许可。宽松许可：允许商业使用、修改和再分发，通常只需在分发时保留原版权与许可声明。以上为概括，不构成法律意见，请以许可原文为准。

### @claudewerk/mutual-key-auth 的下载量有多少？

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

### @claudewerk/mutual-key-auth 还在维护吗？

最新版本 0\.2\.1 发布于 2026/10/27（13 天前）；至今共发布 3 个版本，其中过去 12 个月发布了 3 个；首次发布于 2026/09/28。发布频率只是参考信号：功能稳定的工具可能很少需要更新，建议结合源码仓库的 issue 和提交记录判断。

