@h-ai/crypto 1.0.0-beta.2 → 1.0.0-beta.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +87 -44
- package/README.ja.md +85 -42
- package/README.md +86 -43
- package/dist/index.d.ts +25 -6
- package/dist/index.js +221 -63
- package/dist/index.js.map +1 -1
- package/package.json +10 -10
package/README.md
CHANGED
|
@@ -4,28 +4,11 @@
|
|
|
4
4
|
|
|
5
5
|
[框架首页](../../README.md) · 相关模块:[@h-ai/cache](../cache/README.md) · [@h-ai/core](../core/README.md) · [@h-ai/reldb](../reldb/README.md)
|
|
6
6
|
|
|
7
|
-
## 简介
|
|
8
|
-
|
|
9
7
|
加密模块,提供非对称加密、哈希、对称加密与密码哈希能力。
|
|
10
8
|
|
|
11
|
-
##
|
|
12
|
-
|
|
13
|
-
- 非对称加密(密钥生成、加解密、签名验签)
|
|
14
|
-
- 哈希(哈希、HMAC、验证)
|
|
15
|
-
- 对称加密(ECB/CBC 模式)
|
|
16
|
-
- 密码哈希(加盐迭代哈希)
|
|
17
|
-
- 传输加密(`crypto.transport`:服务端管理器 + 客户端 `encryptedFetch`)
|
|
18
|
-
- SM2/SM3/SM4 与传输支持 Node.js / 浏览器;密码哈希仅服务端
|
|
19
|
-
|
|
20
|
-
## 使用入门
|
|
21
|
-
|
|
22
|
-
### 安全声明
|
|
23
|
-
|
|
24
|
-
- **SM4 默认加密返回结构化密文**:`crypto.symmetric.encrypt(data, key)` 默认使用 CBC + 随机 IV,返回 `{ mode, ciphertext, iv, encoding }`;解密时把该对象传给 `crypto.symmetric.decrypt(payload, key)`。
|
|
25
|
-
- **SM4 使用随机密钥**:通过 `crypto.symmetric.generateKey()` 生成密钥;本模块不提供从密码派生加密密钥的 KDF。密码存储使用服务端 `crypto.password.hash(password)`,不要将密码或密码哈希直接用作 SM4 密钥。
|
|
26
|
-
- **IV 必须唯一**:相同密钥下禁止复用 IV;推荐使用默认 CBC 或 `encryptWithIV()` 每次自动生成。密文与 IV 需一同传输与存储(IV 可公开,不必保密)。
|
|
9
|
+
## 快速开始
|
|
27
10
|
|
|
28
|
-
|
|
11
|
+
并发调用 `close()` 会使尚未完成的 `init()` 失效,迟到初始化不会重新打开模块;等待该次初始化返回后可再次初始化。
|
|
29
12
|
|
|
30
13
|
```ts
|
|
31
14
|
import { crypto } from '@h-ai/crypto'
|
|
@@ -36,7 +19,55 @@ if (!initialized.success)
|
|
|
36
19
|
throw new Error(initialized.error.message)
|
|
37
20
|
```
|
|
38
21
|
|
|
39
|
-
|
|
22
|
+
## 功能
|
|
23
|
+
|
|
24
|
+
### 用途、输入与输出
|
|
25
|
+
|
|
26
|
+
先成功初始化 crypto,再使用算法。HaiResult<T> 先判断 success,成功 data 才是 T;void 没有业务数据,Promise 必须 await。publicKey/privateKey 来自成功的密钥生成,ciphertext、signature、digest、payload 来自相应成功结果。manager 是 createServer 的成功 data,session 是 createClient 直接返回的实例;requestContext/messageContext 由 HTTP 适配器从实际请求取得,不能接收客户端自报的身份。下文给出完整加解密与 HTTP 装配。
|
|
27
|
+
|
|
28
|
+
| 入口 | 使用场景 | 输入(`—` 表示无参数) | 返回值 | 调用示例 |
|
|
29
|
+
| ------------------------------------- | ------------------------- | ------------------------------------------------------------------------- | ------------------------------------------------------ | ------------------------------------------------------------------------------------------------------- |
|
|
30
|
+
| `crypto.init` | 准备加密能力 | `—` | `Promise<HaiResult<void>>` | `await crypto.init()` |
|
|
31
|
+
| `crypto.close` | 释放模块状态 | `—` | `Promise<void>` | `await crypto.close()` |
|
|
32
|
+
| `crypto.isInitialized` | 检查可用状态 | `—` | `boolean` | `crypto.isInitialized` |
|
|
33
|
+
| `crypto.asymmetric.generateKeyPair` | 生成 SM2 密钥对 | `—` | `HaiResult<{ publicKey, privateKey }>` | `crypto.asymmetric.generateKeyPair()` |
|
|
34
|
+
| `crypto.asymmetric.encrypt` | 用接收方公钥加密 | `data: string, publicKey: string, options?: { cipherMode, outputFormat }` | `HaiResult<string>` | `crypto.asymmetric.encrypt("orders", publicKey)` |
|
|
35
|
+
| `crypto.asymmetric.decrypt` | 用私钥解密 | `ciphertext: string, privateKey: string, options?` | `HaiResult<string>` | `crypto.asymmetric.decrypt(ciphertext, privateKey)` |
|
|
36
|
+
| `crypto.asymmetric.sign` | 为消息签名 | `data: string, privateKey: string, options?: { hash, userId }` | `HaiResult<string>` | `crypto.asymmetric.sign("orders", privateKey)` |
|
|
37
|
+
| `crypto.asymmetric.verify` | 核对消息签名 | `data: string, signature: string, publicKey: string, options?` | `HaiResult<boolean>` | `crypto.asymmetric.verify("orders", signature, publicKey)` |
|
|
38
|
+
| `crypto.asymmetric.isValidPublicKey` | 检查公钥格式 | `publicKey: string` | `boolean` | `crypto.asymmetric.isValidPublicKey(publicKey)` |
|
|
39
|
+
| `crypto.asymmetric.isValidPrivateKey` | 检查私钥格式 | `privateKey: string` | `boolean` | `crypto.asymmetric.isValidPrivateKey(privateKey)` |
|
|
40
|
+
| `crypto.hash.hash` | 计算 SM3 摘要 | `data: string / Uint8Array, options?: { inputEncoding }` | `HaiResult<string>: 64 hex` | `crypto.hash.hash("orders")` |
|
|
41
|
+
| `crypto.hash.hmac` | 计算带密钥的摘要 | `data: string, key: string` | `HaiResult<string>` | `crypto.hash.hmac("orders", hmacKey)` |
|
|
42
|
+
| `crypto.hash.verify` | 核对文本与摘要 | `data: string, expectedHash: string` | `HaiResult<boolean>` | `crypto.hash.verify("orders", digest)` |
|
|
43
|
+
| `crypto.symmetric.generateKey` | 生成随机 SM4 密钥 | `—` | `string: 32 hex` | `crypto.symmetric.generateKey()` |
|
|
44
|
+
| `crypto.symmetric.generateIV` | 生成随机初始化向量 | `—` | `string: 32 hex` | `crypto.symmetric.generateIV()` |
|
|
45
|
+
| `crypto.symmetric.encrypt` | 加密并保留解密信息 | `data: string, key: 32 hex, options?: { mode, iv, outputFormat }` | `HaiResult<{ mode, ciphertext, iv?, encoding }>` | `crypto.symmetric.encrypt("orders", key)` |
|
|
46
|
+
| `crypto.symmetric.decrypt` | 解密结构化密文 | `payload: SymmetricEncryptedPayload, key: 32 hex` | `HaiResult<string>` | `crypto.symmetric.decrypt(payload, key)` |
|
|
47
|
+
| `crypto.symmetric.encryptWithIV` | 每次生成新 IV 加密 | `data: string, key: 32 hex` | `HaiResult<{ mode: "cbc", ciphertext, iv, encoding }>` | `crypto.symmetric.encryptWithIV("orders", key)` |
|
|
48
|
+
| `crypto.symmetric.decryptWithIV` | 解密指定 IV 的密文 | `ciphertext: string, key: 32 hex, iv: 32 hex` | `HaiResult<string>` | `crypto.symmetric.decryptWithIV(ciphertext, key, iv)` |
|
|
49
|
+
| `crypto.symmetric.isValidKey` | 检查密钥格式 | `key: string` | `boolean` | `crypto.symmetric.isValidKey(key)` |
|
|
50
|
+
| `crypto.symmetric.isValidIV` | 检查 IV 格式 | `iv: string` | `boolean` | `crypto.symmetric.isValidIV(iv)` |
|
|
51
|
+
| `crypto.password.hash` | 服务端保存密码摘要 | `password: string, options?: { saltLength, iterations }` | `HaiResult<string>: $hai$…` | `crypto.password.hash(password)` |
|
|
52
|
+
| `crypto.password.verify` | 服务端验证密码 | `password: string, storedHash: string` | `HaiResult<boolean>` | `crypto.password.verify(password, storedHash)` |
|
|
53
|
+
| `crypto.transport.createServer` | 创建共享传输管理器 | `{ serverKeyRing, keyStore, maxPlaintextBytes?, … }` | `HaiResult<TransportEncryptionManager>` | `crypto.transport.createServer({ serverKeyRing, keyStore })` |
|
|
54
|
+
| `crypto.transport.createClient` | 创建加密 HTTP 会话 | `{ keyExchangeUrl: HTTPS URL, fetch?, maxPlaintextBytes? }` | `TransportClient: init / encryptedFetch / destroy` | `crypto.transport.createClient({ keyExchangeUrl: "https://api.example.com/api/v1/_hai/key-exchange" })` |
|
|
55
|
+
| `crypto.transport.protocol` | 复用协议路径与头名 | `—` | `TRANSPORT_PROTOCOL_T` | `crypto.transport.protocol` |
|
|
56
|
+
| `manager.getServerKeyId` | 读取当前服务端密钥版本 | `—` | `string` | `manager.getServerKeyId()` |
|
|
57
|
+
| `manager.getServerPublicKey` | 提供握手公钥 | `—` | `string` | `manager.getServerPublicKey()` |
|
|
58
|
+
| `manager.registerClientKey` | 登记客户端公钥 | `clientPublicKey: string` | `Promise<string>: clientId` | `await manager.registerClientKey(clientPublicKey)` |
|
|
59
|
+
| `manager.getClientPublicKey` | 查询共享登记的公钥 | `clientId: string` | `Promise<string / undefined>` | `await manager.getClientPublicKey(clientId)` |
|
|
60
|
+
| `manager.decryptRequest` | 验证并解密请求 | `payload: EncryptedRequestPayload, expected: TransportRequestContext` | `Promise<HaiResult<string>>` | `await manager.decryptRequest(encryptedRequest, requestContext)` |
|
|
61
|
+
| `manager.encryptResponse` | 认证并加密关联响应 | `request: TransportMessageContext, data: string, status: number` | `Promise<HaiResult<EncryptedPayload>>` | `await manager.encryptResponse(messageContext, "{}", 200)` |
|
|
62
|
+
| `manager.close` | 释放管理器资源 | `—` | `Promise<void>` | `await manager.close()` |
|
|
63
|
+
| `session.init` | 显式执行握手 | `—` | `Promise<HaiResult<void>>` | `await session.init()` |
|
|
64
|
+
| `session.encryptedFetch` | 按 fetch 契约调用加密接口 | `input: RequestInfo / URL, init?: RequestInit` | `Promise<Response>` | `await session.encryptedFetch("https://api.example.com/api/v1/orders")` |
|
|
65
|
+
| `session.ready` | 检查握手状态 | `—` | `boolean` | `session.ready()` |
|
|
66
|
+
| `session.destroy` | 清除本地传输会话 | `—` | `void` | `session.destroy()` |
|
|
67
|
+
|
|
68
|
+
SM2/SM3/SM4 与传输支持 Node.js / 浏览器;密码哈希仅服务端
|
|
69
|
+
|
|
70
|
+
### 非对称加密(加解密 / 签名验签)
|
|
40
71
|
|
|
41
72
|
```ts
|
|
42
73
|
// 生成密钥对
|
|
@@ -67,7 +98,7 @@ crypto.asymmetric.isValidPublicKey(publicKey) // true
|
|
|
67
98
|
crypto.asymmetric.isValidPrivateKey(privateKey) // true
|
|
68
99
|
```
|
|
69
100
|
|
|
70
|
-
|
|
101
|
+
### 哈希(Hash / HMAC / 验证)
|
|
71
102
|
|
|
72
103
|
```ts
|
|
73
104
|
// 字符串哈希
|
|
@@ -91,7 +122,7 @@ if (hash.success) {
|
|
|
91
122
|
}
|
|
92
123
|
```
|
|
93
124
|
|
|
94
|
-
|
|
125
|
+
### 对称加密(CBC 默认 / 结构化密文)
|
|
95
126
|
|
|
96
127
|
```ts
|
|
97
128
|
// 生成随机密钥和 IV
|
|
@@ -132,7 +163,7 @@ crypto.symmetric.isValidKey(key) // true
|
|
|
132
163
|
crypto.symmetric.isValidIV(iv) // true
|
|
133
164
|
```
|
|
134
165
|
|
|
135
|
-
|
|
166
|
+
### 密码哈希(加盐迭代)
|
|
136
167
|
|
|
137
168
|
```ts
|
|
138
169
|
// 哈希密码(输出格式: $hai$<iterations>$<salt>$<hash>)
|
|
@@ -154,7 +185,9 @@ if (hashed.success) {
|
|
|
154
185
|
}
|
|
155
186
|
```
|
|
156
187
|
|
|
157
|
-
|
|
188
|
+
### 传输加密(crypto.transport)
|
|
189
|
+
|
|
190
|
+
传输客户端 `destroy()` 使在途握手失效并取消网络等待;迟到结果不能恢复密钥或覆盖下一次握手。它只清理本地会话,服务端 registry 仍按自己的租约回收。
|
|
158
191
|
|
|
159
192
|
规范编码、验证顺序与 nonce 持久化要求见 [Transport v2 协议](TRANSPORT-PROTOCOL.md)。
|
|
160
193
|
|
|
@@ -167,7 +200,7 @@ if (hashed.success) {
|
|
|
167
200
|
|
|
168
201
|
只有在自定义运行时、测试或你确实要自己接 HTTP 协商端点时,才建议直接调用 `crypto.transport.createServer({ serverKeyRing, keyStore })` / `createClient()`。
|
|
169
202
|
|
|
170
|
-
|
|
203
|
+
#### 使用流程
|
|
171
204
|
|
|
172
205
|
1. 先 `await crypto.init()`,确保 `crypto.asymmetric` 与 `crypto.symmetric` 已初始化。
|
|
173
206
|
2. 服务端调用 `crypto.transport.createServer({ serverKeyRing, keyStore })` 创建 `manager`;它持有服务端密钥对,并负责保存客户端公钥。多节点部署时可注入共享 `keyStore`。
|
|
@@ -176,7 +209,7 @@ if (hashed.success) {
|
|
|
176
209
|
5. 每次请求都发送认证封装 `{ context, direction, status, encryptedKey, ciphertext, iv, tag, signature }`;空 POST 和 GET/HEAD 也必须认证。
|
|
177
210
|
6. 自定义适配器先 `await authenticateTransportRequest(manager, request)`,成功后才将 `data.plaintext` 交给业务并保存 `data.context`。
|
|
178
211
|
7. 响应用 `await manager.encryptResponse(context, data, response.status)` 关联原请求;客户端校验上下文、状态和完整性后才返回明文。
|
|
179
|
-
8. 客户端调用 `client.destroy()`
|
|
212
|
+
8. 客户端调用 `client.destroy()` 清空本地会话;服务端先 `await manager.close()` 释放自建管理器,再 `await crypto.close()` 关闭模块。模块不会替应用关闭已经创建的独立管理器,也不因此撤销服务端业务会话。
|
|
180
213
|
|
|
181
214
|
> 服务端必须显式注入相同的受管 `serverKeyRing` 和共享 `keyStore`(Redis 或共享数据库)。仅共享客户端公钥不足以跨节点解密。没有隐式进程私钥或内存 registry 回退;开发/测试显式用 `createInMemoryKeyStore(capacity)`。服务端重启后重新加载同一密钥环即可,无需 sticky session。
|
|
182
215
|
|
|
@@ -190,7 +223,7 @@ if (hashed.success) {
|
|
|
190
223
|
|
|
191
224
|
Redis/DB 的 `ttlSeconds` 省略表示不过期;显式值须为 1–2147483647 的整数秒,建议设置有限 TTL。Redis 使用原生逐键 TTL,不提供 DB 的 `maxClients` 容量限制,部署需设置内存预算且禁止驱逐有效 nonce。DB `maxClients` 为正安全整数,默认 10000,所有节点必须与持久容量记录一致;事务锁定共享容量行后计数和插入,容量满时拒绝注册,不驱逐有效公钥或 nonce。删除客户端不删除尚有效的 nonce。
|
|
192
225
|
|
|
193
|
-
DB 使用数据库时钟生成、判断 epoch 毫秒过期时间;数据库与节点均须满足协议的时钟同步要求。首次建表失败可重试。每次注册自动清理一批;`await registry.pruneExpired()` 每次最多删除 100 条过期公钥和 100 条过期 nonce,可由应用定期调度。没有隐式后台计时器,低流量部署仍需主动调度维护;registry 不关闭应用注入的 reldb
|
|
226
|
+
DB 使用数据库时钟生成、判断 epoch 毫秒过期时间;数据库与节点均须满足协议的时钟同步要求。首次建表失败可重试。每次注册自动清理一批;`await registry.pruneExpired()` 每次最多删除 100 条过期公钥和 100 条过期 nonce,可由应用定期调度。没有隐式后台计时器,低流量部署仍需主动调度维护;registry 不关闭应用注入的 reldb。公钥过期时间保存在 BIGINT 字段 `created_at_ms` / `expires_at_ms`,容量在 `hai_crypto_transport_capacity` 保存;容量调整须协调所有节点及持久记录,不能清空有效 nonce 来重建 registry。
|
|
194
227
|
|
|
195
228
|
```ts
|
|
196
229
|
const registry = createReldbTransportKeyStore({ reldb, ttlSeconds: 3600, maxClients: 10000 })
|
|
@@ -246,17 +279,17 @@ const serverKeyRing = ring.data
|
|
|
246
279
|
const server = crypto.transport.createServer({ serverKeyRing, keyStore })
|
|
247
280
|
```
|
|
248
281
|
|
|
249
|
-
握手返回 `{ serverKeyId, serverPublicKey, clientId }`;请求密文的 `context` 携带 `serverKeyId`,服务端按 ID 选择私钥,未知或撤销 ID 直接拒绝,不尝试其它密钥。响应仍用客户端公钥加密。轮换先向所有节点分发包含新旧版本的 ring,再切换 `activeKeyId` 并滚动重建 manager;过渡期保留旧版本,结束后删除旧 ID 并更新所有节点。旧客户端此后须 `destroy()` 后重新协商;不要自动重试非幂等业务。部署通过 Secret Manager 或服务端私有环境分发同一 ring,禁止写入普通 YAML、PUBLIC_/VITE_、日志或公开响应。CLI 创建服务端项目时为 `.env.dev/.env.test/.env.prod`
|
|
282
|
+
握手返回 `{ serverKeyId, serverPublicKey, clientId }`;请求密文的 `context` 携带 `serverKeyId`,服务端按 ID 选择私钥,未知或撤销 ID 直接拒绝,不尝试其它密钥。响应仍用客户端公钥加密。轮换先向所有节点分发包含新旧版本的 ring,再切换 `activeKeyId` 并滚动重建 manager;过渡期保留旧版本,结束后删除旧 ID 并更新所有节点。旧客户端此后须 `destroy()` 后重新协商;不要自动重试非幂等业务。部署通过 Secret Manager 或服务端私有环境分发同一 ring,禁止写入普通 YAML、PUBLIC_/VITE_、日志或公开响应。CLI 创建服务端项目时为 `.env.dev.local/.env.test.local/.env.prod.local` 分别生成独立密钥环,文件不提交 Git;克隆后由受保护配置或 Secret Manager 分发对应环境秘密,也可通过私有环境或 Secret Manager 覆盖。运行时不生成密钥。
|
|
250
283
|
|
|
251
|
-
认证协议仅支持 v2:独立随机 SM4-CBC 密钥和 HMAC-SM3 密钥由 SM2 封装,先验签、再验证 MAC,最后解密 CBC。请求由客户端私钥签名,响应由所选服务端版本签名。认证字段包括 version、direction、serverKeyId、clientId、128 位随机 requestId(兼 nonce)、issuedAt、HTTP method、pathname + 原始 search 的 SM3 pathHash,以及响应 status
|
|
284
|
+
认证协议仅支持 v2:独立随机 SM4-CBC 密钥和 HMAC-SM3 密钥由 SM2 封装,先验签、再验证 MAC,最后解密 CBC。请求由客户端私钥签名,响应由所选服务端版本签名。认证字段包括 version、direction、serverKeyId、clientId、128 位随机 requestId(兼 nonce)、issuedAt、HTTP method、pathname + 原始 search 的 SM3 pathHash,以及响应 status。请求默认有效期为 5 分钟,客户端时间最多可领先服务器 30 秒。服务端可用 `maxRequestAgeSeconds`(1~600)和 `maxFutureSkewSeconds`(0~300)调整,单位为整数秒。共享 registry 原子消费 (clientId, requestId),nonce 保留到 issuedAt + 11 分钟,覆盖最长可配窗口及节点时钟差,存储故障拒绝请求。Redis 必须保留有效 nonce,使用 noeviction/可靠持久化策略;各节点时钟须同步。此能力防止同一传输消息重放,业务重试仍需自己的幂等键。
|
|
252
285
|
|
|
253
286
|
空 POST 也发送空明文的认证封装;GET/HEAD 将封装放在 X-Hai-Transport 头,204/304/HEAD 等无正文响应也使用同名头承载认证封装。其它请求/响应在 JSON 正文承载封装并带 X-Encrypted: true。客户端必须关联原请求上下文和 HTTP status,缺标记、旧格式或认证失败一律 reject;明文接口由 Serv/Kit 路径规则 `encryption: disabled` 或宿主 `excludePaths` 显式允许;调用方同步选择普通请求,API Client/Kit 包装器使用自己的 `excludePaths`。CORS 须允许并暴露 X-Hai-Transport。握手公钥的真实性依赖 TLS,不替代 IAM 业务授权。
|
|
254
287
|
|
|
255
|
-
业务容量通过 `transport.maxPlaintextBytes` 统一配置:默认 1 MiB,整数范围 1 KiB~64 MiB,请求和响应共用 UTF-8 明文字节预算。客户端从握手自动获取上限,密文与部署代理预算由 `resolveTransportBodyLimits()` 推导(两倍明文 + 32 KiB)。握手、公钥、认证头和防重放约束独立;超限取消原流并释放 reader,请求 413、响应为加密的 500 容量错误,客户端识别 `TransportBodyTooLargeError` / `hai:crypto:070`。 [
|
|
288
|
+
业务容量通过 `transport.maxPlaintextBytes` 统一配置:默认 1 MiB,整数范围 1 KiB~64 MiB,请求和响应共用 UTF-8 明文字节预算。客户端从握手自动获取上限,密文与部署代理预算由 `resolveTransportBodyLimits()` 推导(两倍明文 + 32 KiB)。握手、公钥、认证头和防重放约束独立;超限取消原流并释放 reader,请求 413、响应为加密的 500 容量错误,客户端识别 `TransportBodyTooLargeError` / `hai:crypto:070`。 [容量配置与文件换算](TRANSPORT-CAPACITY.md)。
|
|
256
289
|
|
|
257
290
|
普通业务仍使用 `transport.maxPlaintextBytes`(auto/省略为 1 MiB);Feedback 的报告受理、证据、附件及 ZIP 下载路由通过 `routePlaintextBytes` 单独声明 `feedback.budget.maxTransportBytes`。路径中的 `*` 仅匹配一个完整路径段;宿主显式路由值优先。握手、adapter-node 与代理使用各路由最大容量,普通业务不会随材料预算扩大。HTTP 适配器为单独声明且大于 1 MiB 的材料路由保留最多 2 个槽,普通业务另有 16 个槽,完整响应/失败后释放;忙碌返回 503 / `hai:crypto:072`,不自动重放业务写入。
|
|
258
291
|
|
|
259
|
-
|
|
292
|
+
Serv/Kit 对通过请求绑定、签名和 MAC 校验但超出时间窗口的请求返回加密的 HTTP 400 错误。客户端抛出 `TransportTimeError`(`hai:crypto:071`),显示校验时的服务器 UTC 时间,提示开启设备的自动校时或手工修改时间后刷新。`serverTime` 为 Unix 毫秒,旧服务端未提供该值时仍显示校时提示。`api-client` 映射为 `VALIDATION_FAILED`,在 `cause.transportCode` 和 `cause.serverTime` 保留诊断信息。
|
|
260
293
|
|
|
261
294
|
自定义 HTTP 适配器检查失败原因 `error instanceof TransportTimeError && error.context` 后,使用 `createTransportErrorResponse(error, { manager, context: error.context })` 加密拒绝响应;不能根据未认证报文的时间戳给出可信时间诊断。
|
|
262
295
|
|
|
@@ -272,33 +305,32 @@ const text = await readTransportBody(request.body, {
|
|
|
272
305
|
const payload: unknown = JSON.parse(text)
|
|
273
306
|
```
|
|
274
307
|
|
|
275
|
-
|
|
308
|
+
### 关闭模块
|
|
276
309
|
|
|
277
310
|
```ts
|
|
278
311
|
// 使用完毕后关闭,释放内部状态
|
|
279
312
|
await crypto.close()
|
|
280
313
|
```
|
|
281
314
|
|
|
282
|
-
###
|
|
283
|
-
|
|
284
|
-
所有操作返回 `HaiResult<T>`:
|
|
315
|
+
### 安全声明
|
|
285
316
|
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
// result.error.code / result.error.message
|
|
290
|
-
}
|
|
291
|
-
```
|
|
317
|
+
- **SM4 默认加密返回结构化密文**:`crypto.symmetric.encrypt(data, key)` 默认使用 CBC + 随机 IV,返回 `{ mode, ciphertext, iv, encoding }`;解密时把该对象传给 `crypto.symmetric.decrypt(payload, key)`。
|
|
318
|
+
- **SM4 使用随机密钥**:通过 `crypto.symmetric.generateKey()` 生成密钥;本模块不提供从密码派生加密密钥的 KDF。密码存储使用服务端 `crypto.password.hash(password)`,不要将密码或密码哈希直接用作 SM4 密钥。
|
|
319
|
+
- **IV 必须唯一**:相同密钥下禁止复用 IV;推荐使用默认 CBC 或 `encryptWithIV()` 每次自动生成。密文与 IV 需一同传输与存储(IV 可公开,不必保密)。
|
|
292
320
|
|
|
293
321
|
### 可选集成
|
|
294
322
|
|
|
295
323
|
管理端在线加密演示直接调用 Crypto SM3/SM4 API,SM4 使用随机临时密钥和 IV,并在显示成功前解密校验;密钥不保存。输入改变即清空旧结果,禁止用随机摘要或 Base64 伪装算法结果。
|
|
296
324
|
|
|
297
|
-
##
|
|
325
|
+
## 配置
|
|
326
|
+
|
|
327
|
+
crypto.init() 不接收配置对象。算法选项由每次调用传入;HTTP 传输由 crypto.transport.createServer/createClient 的选项配置,密钥环和共享 registry 由服务端显式注入。输入及默认值见下文,私钥不进入浏览器或公共 YAML。
|
|
328
|
+
|
|
329
|
+
### 生产共享状态
|
|
298
330
|
|
|
299
331
|
生产传输 registry 必须声明 stateScope=shared 并实际使用 Redis 或 PostgreSQL/MySQL。memory、包装 memory 的 Redis 工厂、包装 SQLite 的 RelDB 工厂会被拒绝;各节点加载相同受管版本化密钥环。 NODE_ENV 或 HAI_CORE_ENV 为 production 时在初始化发布成功前校验;仅 HAI_E2E=1/HAI_E2E_MOCK=1 明确测试可豁免。 [状态、并发和恢复边界](../../.agents/instructions/distributed-state.md)。
|
|
300
332
|
|
|
301
|
-
|
|
333
|
+
### 模块容量与显式上限
|
|
302
334
|
|
|
303
335
|
`resolveTransportBodyLimits(limit?, { requiredPlaintextBytes? })` 只在 limit 未设置时按模块需求推导,无模块需求时仍为 1 MiB。显式上限小于模块需求时抛出 RangeError,不能静默扩大或缩小。所有值仍须在 1 KiB~64 MiB 范围;密文预算为明文两倍加 32 KiB。Serv/Kit 配置可用 `auto` 表示未显式限额,运行时将它规范化为 undefined。
|
|
304
336
|
|
|
@@ -309,6 +341,17 @@ const limits = resolveTransportBodyLimits(24 * 1024 * 1024, { requiredPlaintextB
|
|
|
309
341
|
// limits.plaintextBytes === 25165824;limits.encryptedPayloadBytes === 50364416
|
|
310
342
|
```
|
|
311
343
|
|
|
344
|
+
## 错误处理
|
|
345
|
+
|
|
346
|
+
加解密、签名、哈希和密码操作返回 `HaiResult<T>`,先判断 `success`。随机 key/IV 与格式检测工具返回直接值;`crypto.close()` 返回 `Promise<void>`。`encryptedFetch` 按 fetch 语义拒绝 Promise,流读取、容量推导等显式抛错工具须在各自调用边界捕获:
|
|
347
|
+
|
|
348
|
+
```ts
|
|
349
|
+
const result = crypto.asymmetric.encrypt('data', publicKey)
|
|
350
|
+
if (!result.success) {
|
|
351
|
+
// result.error.code / result.error.message
|
|
352
|
+
}
|
|
353
|
+
```
|
|
354
|
+
|
|
312
355
|
## 开发测试
|
|
313
356
|
|
|
314
357
|
```bash
|
package/dist/index.d.ts
CHANGED
|
@@ -165,9 +165,9 @@ declare const TRANSPORT_PROTOCOL: {
|
|
|
165
165
|
readonly ENVELOPE_HEADER: "X-Hai-Transport";
|
|
166
166
|
/** 唯一支持的认证协议版本;旧未认证格式拒绝。 */
|
|
167
167
|
readonly VERSION: 2;
|
|
168
|
-
/**
|
|
168
|
+
/** 默认请求有效期(毫秒)。 */
|
|
169
169
|
readonly REQUEST_WINDOW_MS: 300000;
|
|
170
|
-
/**
|
|
170
|
+
/** 默认允许的客户端未来偏差(毫秒),也是部署约定的节点对时误差上限。 */
|
|
171
171
|
readonly CLOCK_SKEW_MS: 30000;
|
|
172
172
|
/** 标识响应体已加密的响应头名。 */
|
|
173
173
|
readonly ENCRYPTED_HEADER: "X-Encrypted";
|
|
@@ -176,6 +176,19 @@ declare const TRANSPORT_PROTOCOL: {
|
|
|
176
176
|
/** 密钥协商端点的默认子路径(相对于挂载前缀)。 */
|
|
177
177
|
readonly DEFAULT_KEY_EXCHANGE_PATH: "/_hai/key-exchange";
|
|
178
178
|
};
|
|
179
|
+
/**
|
|
180
|
+
* 服务端传输时间配置的允许范围,供 Crypto 工厂与 Serv/Kit 配置共用。
|
|
181
|
+
* @example
|
|
182
|
+
* ```ts
|
|
183
|
+
* import { TRANSPORT_TIME_LIMITS } from '@h-ai/crypto'
|
|
184
|
+
* const latest = TRANSPORT_TIME_LIMITS.maxRequestAgeSeconds
|
|
185
|
+
* ```
|
|
186
|
+
*/
|
|
187
|
+
declare const TRANSPORT_TIME_LIMITS: Readonly<{
|
|
188
|
+
maxFutureSkewSeconds: 300;
|
|
189
|
+
minRequestAgeSeconds: 1;
|
|
190
|
+
maxRequestAgeSeconds: 600;
|
|
191
|
+
}>;
|
|
179
192
|
/**
|
|
180
193
|
* 密钥协商请求;只发送客户端公钥,私钥始终留在客户端,注册仍验证曲线点。
|
|
181
194
|
*
|
|
@@ -296,7 +309,7 @@ interface TransportMessageContext {
|
|
|
296
309
|
readonly clientId: string;
|
|
297
310
|
/** 128 位随机十六进制 nonce,同时用于响应关联。 */
|
|
298
311
|
readonly requestId: string;
|
|
299
|
-
/** 请求创建时间,Unix
|
|
312
|
+
/** 请求创建时间,Unix 毫秒;时间窗口由服务端配置。 */
|
|
300
313
|
readonly issuedAt: number;
|
|
301
314
|
/** 大写 HTTP 方法,必须与实际 Request 完全一致。 */
|
|
302
315
|
readonly method: string;
|
|
@@ -573,6 +586,10 @@ interface TransportKeyStore {
|
|
|
573
586
|
* ```
|
|
574
587
|
*/
|
|
575
588
|
interface TransportCreateServerOptions {
|
|
589
|
+
/** 请求时间允许领先服务器的秒数,默认 30,整数范围 0~300。 */
|
|
590
|
+
readonly maxFutureSkewSeconds?: number;
|
|
591
|
+
/** 请求从签发起的有效秒数,默认 300,整数范围 1~600。 */
|
|
592
|
+
readonly maxRequestAgeSeconds?: number;
|
|
576
593
|
/** 双向业务明文 UTF-8 字节上限;默认 1 MiB,整数范围 1 KiB~64 MiB。 */
|
|
577
594
|
readonly maxPlaintextBytes?: number;
|
|
578
595
|
/** 必填受管服务端密钥环;没有隐式随机生成或进程密钥回退。 */
|
|
@@ -1944,7 +1961,7 @@ declare function isValidSM2PublicKey(publicKey: unknown): publicKey is string;
|
|
|
1944
1961
|
/** @h-ai/crypto — serv/kit 共用的安全传输错误响应。 */
|
|
1945
1962
|
|
|
1946
1963
|
/**
|
|
1947
|
-
*
|
|
1964
|
+
* 已认证请求的时间窗口错误;附带校验失败时的服务器时间(Unix 毫秒)。
|
|
1948
1965
|
* @example
|
|
1949
1966
|
* ```ts
|
|
1950
1967
|
* import { TransportTimeError } from '@h-ai/crypto'
|
|
@@ -1955,7 +1972,8 @@ declare function isValidSM2PublicKey(publicKey: unknown): publicKey is string;
|
|
|
1955
1972
|
declare class TransportTimeError extends Error {
|
|
1956
1973
|
readonly context?: TransportMessageContext | undefined;
|
|
1957
1974
|
readonly code: string | number;
|
|
1958
|
-
|
|
1975
|
+
readonly serverTime?: number;
|
|
1976
|
+
constructor(context?: TransportMessageContext | undefined, serverTime?: number);
|
|
1959
1977
|
}
|
|
1960
1978
|
/**
|
|
1961
1979
|
* 创建脱敏传输错误;已验证绑定、签名和 MAC 的时间/响应容量错误继续加密,请求读取超限返回 HTTP 413。
|
|
@@ -2211,4 +2229,5 @@ interface ReldbTransportKeyStore extends TransportKeyStore {
|
|
|
2211
2229
|
*/
|
|
2212
2230
|
declare function createReldbTransportKeyStore(options: CreateReldbTransportKeyStoreOptions): ReldbTransportKeyStore;
|
|
2213
2231
|
|
|
2214
|
-
export {
|
|
2232
|
+
export { HaiCryptoError, TRANSPORT_BODY_LIMITS, TRANSPORT_PLAINTEXT_RANGE, TRANSPORT_PROTOCOL, TRANSPORT_TIME_LIMITS, TransportBodyTooLargeError, TransportTimeError, authenticateTransportRequest, createInMemoryKeyStore, createRedisTransportKeyStore, createReldbTransportKeyStore, createTransportBodyPolicy, createTransportErrorResponse, crypto, isValidSM2PublicKey, parseTransportServerKeyRing, readTransportBody, resolveTransportBodyLimits };
|
|
2233
|
+
export type { AsymmetricEncryptOptions, AsymmetricOperations, CipherMode, CreateRedisTransportKeyStoreOptions, CreateReldbTransportKeyStoreOptions, CryptoFunctions, EncryptWithIVResult, EncryptedPayload, EncryptedRequestPayload, HashOperations, HashOptions, KeyExchangeRequest, KeyExchangeResponse, KeyPair, PasswordConfig, PasswordOperations, ReadTransportBodyOptions, ReldbTransportKeyStore, SignOptions, SymmetricCbcEncryptedPayload, SymmetricCiphertextEncoding, SymmetricDecryptInput, SymmetricEncryptOptions, SymmetricEncryptedPayload, SymmetricMode, SymmetricOperations, TransportBodyLimits, TransportClient, TransportCreateServerOptions, TransportCryptoServiceLike, TransportEncryptionManager, TransportKeyPair, TransportKeyStore, TransportMessageContext, TransportOperations, TransportRequestContext, TransportServerKeyRing };
|