@h-ai/crypto 0.1.0-alpha.66 → 0.1.0-alpha.68
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 +86 -31
- package/README.ja.md +86 -31
- package/README.md +88 -33
- package/dist/index.d.ts +1625 -173
- package/dist/index.js +719 -230
- package/dist/index.js.map +1 -1
- package/package.json +5 -4
package/dist/index.d.ts
CHANGED
|
@@ -13,13 +13,27 @@ import { ReldbFunctions } from '@h-ai/reldb';
|
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
|
-
*
|
|
16
|
+
* 认证传输v2的头名、窗口与默认协商子路径;各HTTP适配器共用,不手写漂移常量。
|
|
17
17
|
*
|
|
18
|
-
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```ts
|
|
20
|
+
* import { TRANSPORT_PROTOCOL } from '@h-ai/crypto'
|
|
21
|
+
* const headers = new Headers()
|
|
22
|
+
* headers.set(TRANSPORT_PROTOCOL.ENCRYPTED_HEADER, TRANSPORT_PROTOCOL.ENCRYPTED_HEADER_VALUE)
|
|
23
|
+
* const keyExchangePath = TRANSPORT_PROTOCOL.DEFAULT_KEY_EXCHANGE_PATH
|
|
24
|
+
* ```
|
|
19
25
|
*/
|
|
20
26
|
declare const TRANSPORT_PROTOCOL: {
|
|
21
27
|
/** 客户端 ID 请求头名。 */
|
|
22
28
|
readonly CLIENT_ID_HEADER: "X-Client-Id";
|
|
29
|
+
/** GET/HEAD 等无正文请求的认证封装;禁止与正文封装同时出现。 */
|
|
30
|
+
readonly ENVELOPE_HEADER: "X-Hai-Transport";
|
|
31
|
+
/** 唯一支持的认证协议版本;旧未认证格式拒绝。 */
|
|
32
|
+
readonly VERSION: 2;
|
|
33
|
+
/** 请求允许的过去时间窗口(毫秒)。 */
|
|
34
|
+
readonly REQUEST_WINDOW_MS: 300000;
|
|
35
|
+
/** 节点时钟允许的最大未来偏差(毫秒)。 */
|
|
36
|
+
readonly CLOCK_SKEW_MS: 30000;
|
|
23
37
|
/** 标识响应体已加密的响应头名。 */
|
|
24
38
|
readonly ENCRYPTED_HEADER: "X-Encrypted";
|
|
25
39
|
/** `X-Encrypted` 响应头的开启值。 */
|
|
@@ -28,45 +42,218 @@ declare const TRANSPORT_PROTOCOL: {
|
|
|
28
42
|
readonly DEFAULT_KEY_EXCHANGE_PATH: "/_hai/key-exchange";
|
|
29
43
|
};
|
|
30
44
|
/**
|
|
31
|
-
*
|
|
45
|
+
* 密钥协商请求;只发送客户端公钥,私钥始终留在客户端,注册仍验证曲线点。
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* ```ts
|
|
49
|
+
* import { crypto, type KeyExchangeRequest } from '@h-ai/crypto'
|
|
50
|
+
* const initialized = await crypto.init()
|
|
51
|
+
* if (!initialized.success) throw new Error(initialized.error.message)
|
|
52
|
+
* try {
|
|
53
|
+
* const generated = crypto.asymmetric.generateKeyPair()
|
|
54
|
+
* if (!generated.success) throw new Error(generated.error.message)
|
|
55
|
+
* const pair = generated.data
|
|
56
|
+
* const body: KeyExchangeRequest = { clientPublicKey: pair.publicKey }
|
|
57
|
+
* const serialized = JSON.stringify(body)
|
|
58
|
+
* }
|
|
59
|
+
* finally { await crypto.close() }
|
|
60
|
+
* ```
|
|
32
61
|
*/
|
|
33
62
|
interface KeyExchangeRequest {
|
|
34
63
|
readonly clientPublicKey: string;
|
|
35
64
|
}
|
|
36
65
|
/**
|
|
37
|
-
*
|
|
66
|
+
* TLS协商响应,公开服务端版本、公钥和registry生成的clientId;不含服务端私钥。
|
|
67
|
+
*
|
|
68
|
+
* @example
|
|
69
|
+
* ```ts
|
|
70
|
+
* import type { KeyExchangeRequest, KeyExchangeResponse, TransportEncryptionManager } from '@h-ai/crypto'
|
|
71
|
+
* async function exchange(manager: TransportEncryptionManager, body: KeyExchangeRequest): Promise<KeyExchangeResponse> {
|
|
72
|
+
* const clientId = await manager.registerClientKey(body.clientPublicKey)
|
|
73
|
+
* return { clientId, serverKeyId: manager.getServerKeyId(), serverPublicKey: manager.getServerPublicKey() }
|
|
74
|
+
* }
|
|
75
|
+
* ```
|
|
38
76
|
*/
|
|
39
77
|
interface KeyExchangeResponse {
|
|
78
|
+
/** 服务端当前加密密钥版本;业务请求必须带回该值。 */
|
|
79
|
+
readonly serverKeyId: string;
|
|
40
80
|
readonly serverPublicKey: string;
|
|
41
81
|
readonly clientId: string;
|
|
42
82
|
}
|
|
43
|
-
/**
|
|
83
|
+
/**
|
|
84
|
+
* 传输使用的SM2公私钥对;服务端版本从私有Secret注入,客户端可本地生成独立会话密钥。
|
|
85
|
+
*
|
|
86
|
+
* @example
|
|
87
|
+
* ```ts
|
|
88
|
+
* import { crypto, type TransportKeyPair } from '@h-ai/crypto'
|
|
89
|
+
* const initialized = await crypto.init()
|
|
90
|
+
* if (!initialized.success) throw new Error(initialized.error.message)
|
|
91
|
+
* try {
|
|
92
|
+
* const generated = crypto.asymmetric.generateKeyPair()
|
|
93
|
+
* if (!generated.success) throw new Error(generated.error.message)
|
|
94
|
+
* const pair = generated.data
|
|
95
|
+
* const clientPair: TransportKeyPair = pair
|
|
96
|
+
* const publicKey = clientPair.publicKey
|
|
97
|
+
* }
|
|
98
|
+
* finally { await crypto.close() }
|
|
99
|
+
* ```
|
|
100
|
+
*/
|
|
44
101
|
interface TransportKeyPair {
|
|
45
102
|
readonly publicKey: string;
|
|
46
103
|
readonly privateKey: string;
|
|
47
104
|
}
|
|
48
105
|
/**
|
|
49
|
-
*
|
|
106
|
+
* 完整v2认证封装;encryptedKey含独立SM4/HMAC密钥,signature覆盖认证字段和MAC。
|
|
107
|
+
* HTTP适配器只用成功认证得到的上下文生成响应,不自行拼接示例密文。
|
|
50
108
|
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
109
|
+
* @example
|
|
110
|
+
* ```ts
|
|
111
|
+
* import { authenticateTransportRequest, type EncryptedPayload, type TransportEncryptionManager } from '@h-ai/crypto'
|
|
112
|
+
* async function sealResponse(manager: TransportEncryptionManager, request: Request): Promise<EncryptedPayload> {
|
|
113
|
+
* const verified = await authenticateTransportRequest(manager, request)
|
|
114
|
+
* if (!verified.success) throw new Error(verified.error.message)
|
|
115
|
+
* const encrypted = await manager.encryptResponse(verified.data.context, JSON.stringify({ accepted: true }), 200)
|
|
116
|
+
* if (!encrypted.success) throw new Error(encrypted.error.message)
|
|
117
|
+
* return encrypted.data
|
|
118
|
+
* }
|
|
119
|
+
* ```
|
|
53
120
|
*/
|
|
54
121
|
interface EncryptedPayload {
|
|
122
|
+
/** 认证的请求上下文;响应必须原样关联同一个请求。 */
|
|
123
|
+
readonly context: TransportMessageContext;
|
|
124
|
+
/** 认证方向,防止把合法响应反射到请求入口。 */
|
|
125
|
+
readonly direction: 'request' | 'response';
|
|
126
|
+
/** 请求固定为 0;响应认证实际 HTTP 状态码。 */
|
|
127
|
+
readonly status: number;
|
|
128
|
+
/** SM2 封装的 JSON [SM4 密钥 hex, 独立 HMAC 密钥 hex]。 */
|
|
55
129
|
readonly encryptedKey: string;
|
|
130
|
+
/** SM4-CBC 密文 hex;认证输入包含原始编码值。 */
|
|
56
131
|
readonly ciphertext: string;
|
|
132
|
+
/** 随机 16 字节 CBC IV 的 hex 编码。 */
|
|
57
133
|
readonly iv: string;
|
|
134
|
+
/** 独立随机密钥计算的 HMAC-SM3;先验证后解密 CBC。 */
|
|
135
|
+
readonly tag: string;
|
|
136
|
+
/** 对完整封装和 tag 的发送方 SM2 签名,防止公钥持有者伪造发送方。 */
|
|
137
|
+
readonly signature: string;
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* 认证的单次HTTP上下文;128位nonce、版本、客户端、时间、方法和实际路径SM3摘要共同绑定。
|
|
141
|
+
* 服务端共享registry只允许消费一次nonce;响应必须关联成功认证后的同一上下文。
|
|
142
|
+
*
|
|
143
|
+
* @example
|
|
144
|
+
* ```ts
|
|
145
|
+
* import { authenticateTransportRequest, type TransportEncryptionManager, type TransportMessageContext } from '@h-ai/crypto'
|
|
146
|
+
* async function contextOf(manager: TransportEncryptionManager, request: Request): Promise<TransportMessageContext> {
|
|
147
|
+
* const verified = await authenticateTransportRequest(manager, request)
|
|
148
|
+
* if (!verified.success) throw new Error(verified.error.message)
|
|
149
|
+
* return verified.data.context
|
|
150
|
+
* }
|
|
151
|
+
* ```
|
|
152
|
+
*/
|
|
153
|
+
interface TransportMessageContext {
|
|
154
|
+
/** 唯一受支持的协议版本。 */
|
|
155
|
+
readonly version: 2;
|
|
156
|
+
/** 协商选择的受管服务端密钥版本。 */
|
|
157
|
+
readonly serverKeyId: string;
|
|
158
|
+
/** registry 注册的客户端 ID;发送方必须证明持有对应私钥。 */
|
|
159
|
+
readonly clientId: string;
|
|
160
|
+
/** 128 位随机十六进制 nonce,同时用于响应关联。 */
|
|
161
|
+
readonly requestId: string;
|
|
162
|
+
/** 请求创建时间,Unix 毫秒;允许过去 5 分钟、未来 30 秒。 */
|
|
163
|
+
readonly issuedAt: number;
|
|
164
|
+
/** 大写 HTTP 方法,必须与实际 Request 完全一致。 */
|
|
165
|
+
readonly method: string;
|
|
166
|
+
/** 实际 pathname + 原始 search 的 SM3 摘要,64 位小写 hex,避免在认证头中重复长 URL。 */
|
|
167
|
+
readonly pathHash: string;
|
|
168
|
+
}
|
|
169
|
+
/**
|
|
170
|
+
* 从实际HTTP Request提取的可信绑定输入;不能直接复制密文的自报方法、路径或客户端。
|
|
171
|
+
*
|
|
172
|
+
* @example
|
|
173
|
+
* ```ts
|
|
174
|
+
* import type { TransportRequestContext } from '@h-ai/crypto'
|
|
175
|
+
* function expectedContext(request: Request): TransportRequestContext {
|
|
176
|
+
* const clientId = request.headers.get('X-Client-Id')
|
|
177
|
+
* if (!clientId) throw new Error('Missing client ID')
|
|
178
|
+
* const url = new URL(request.url)
|
|
179
|
+
* return { clientId, method: request.method, path: url.pathname + url.search }
|
|
180
|
+
* }
|
|
181
|
+
* ```
|
|
182
|
+
*/
|
|
183
|
+
interface TransportRequestContext {
|
|
184
|
+
/** 实际 X-Client-Id 头,用于 registry 查询及发送方验签。 */
|
|
185
|
+
readonly clientId: string;
|
|
186
|
+
/** 实际 Request.method。 */
|
|
187
|
+
readonly method: string;
|
|
188
|
+
/** 实际 URL.pathname + URL.search,保留查询参数顺序及编码。 */
|
|
189
|
+
readonly path: string;
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* 客户端发出的完整请求封装,direction=request且status=0;结构类型不构成认证证明。
|
|
193
|
+
*
|
|
194
|
+
* @example
|
|
195
|
+
* ```ts
|
|
196
|
+
* import type { EncryptedRequestPayload, TransportEncryptionManager } from '@h-ai/crypto'
|
|
197
|
+
* async function decrypt(manager: TransportEncryptionManager, request: Request, payload: EncryptedRequestPayload): Promise<string> {
|
|
198
|
+
* const clientId = request.headers.get('X-Client-Id')
|
|
199
|
+
* if (!clientId) throw new Error('Missing client ID')
|
|
200
|
+
* const url = new URL(request.url)
|
|
201
|
+
* const result = await manager.decryptRequest(payload, { clientId, method: request.method, path: url.pathname + url.search })
|
|
202
|
+
* if (!result.success) throw new Error(result.error.message)
|
|
203
|
+
* return result.data
|
|
204
|
+
* }
|
|
205
|
+
* ```
|
|
206
|
+
*/
|
|
207
|
+
interface EncryptedRequestPayload extends EncryptedPayload {
|
|
208
|
+
readonly direction: 'request';
|
|
209
|
+
readonly status: 0;
|
|
58
210
|
}
|
|
59
211
|
/**
|
|
60
|
-
*
|
|
212
|
+
* 服务端受管版本密钥环;所有节点同版本对应同密钥,新协商选activeKeyId,旧版本保留到撤销。
|
|
213
|
+
* 先分发新旧版本再切活动ID;删除旧版本即撤销,私钥不进入PUBLIC_/VITE_/日志。
|
|
61
214
|
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
215
|
+
* @example
|
|
216
|
+
* ```ts
|
|
217
|
+
* import { parseTransportServerKeyRing, type TransportServerKeyRing } from '@h-ai/crypto'
|
|
218
|
+
* const parsed = parseTransportServerKeyRing(process.env.HAI_CRYPTO_TRANSPORT_SERVERKEYRING)
|
|
219
|
+
* if (!parsed.success) throw new Error(parsed.error.message)
|
|
220
|
+
* const ring: TransportServerKeyRing = parsed.data
|
|
221
|
+
* ```
|
|
222
|
+
*/
|
|
223
|
+
interface TransportServerKeyRing {
|
|
224
|
+
/** 新协商使用的版本;1–64 个字母、数字、下划线或连字符。 */
|
|
225
|
+
readonly activeKeyId: string;
|
|
226
|
+
/** ID 与密钥对;仅供服务端保管,所有节点必须分发相同版本的相同密钥。 */
|
|
227
|
+
readonly keys: Readonly<Record<string, TransportKeyPair>>;
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* 传输协议实际需要的SM2/SM3/HMAC/SM4能力;可结构化注入真实crypto,不仿造另一套算法。
|
|
231
|
+
*
|
|
232
|
+
* @example
|
|
233
|
+
* ```ts
|
|
234
|
+
* import { crypto, type TransportCryptoServiceLike } from '@h-ai/crypto'
|
|
235
|
+
* const initialized = await crypto.init()
|
|
236
|
+
* if (!initialized.success) throw new Error(initialized.error.message)
|
|
237
|
+
* try {
|
|
238
|
+
* const service: TransportCryptoServiceLike = crypto
|
|
239
|
+
* const digest = service.hash.hash('/api/v1/orders')
|
|
240
|
+
* if (!digest.success) throw new Error(digest.error.message)
|
|
241
|
+
* }
|
|
242
|
+
* finally { await crypto.close() }
|
|
243
|
+
* ```
|
|
64
244
|
*/
|
|
65
245
|
interface TransportCryptoServiceLike {
|
|
66
246
|
asymmetric: {
|
|
67
247
|
generateKeyPair: () => HaiResult<TransportKeyPair>;
|
|
68
248
|
encrypt: (data: string, publicKey: string) => HaiResult<string>;
|
|
69
249
|
decrypt: (ciphertext: string, privateKey: string) => HaiResult<string>;
|
|
250
|
+
sign: (data: string, privateKey: string) => HaiResult<string>;
|
|
251
|
+
verify: (data: string, signature: string, publicKey: string) => HaiResult<boolean>;
|
|
252
|
+
};
|
|
253
|
+
hash: {
|
|
254
|
+
/** SM3 摘要,用于实际请求路径绑定,不使用其它摘要算法替换。 */
|
|
255
|
+
hash: (data: string) => HaiResult<string>;
|
|
256
|
+
hmac: (data: string, key: string) => HaiResult<string>;
|
|
70
257
|
};
|
|
71
258
|
symmetric: {
|
|
72
259
|
generateKey: () => string;
|
|
@@ -78,93 +265,467 @@ interface TransportCryptoServiceLike {
|
|
|
78
265
|
};
|
|
79
266
|
}
|
|
80
267
|
/**
|
|
81
|
-
*
|
|
268
|
+
* 客户端公钥与nonce存储契约;生产显式共享Redis/数据库,真实共享与原子性由提供者保证。
|
|
269
|
+
* get的故障不能当未注册,claimRequest故障不能当成功;有效nonce不得在expiresAt前驱逐。
|
|
82
270
|
*
|
|
83
|
-
*
|
|
84
|
-
*
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
*
|
|
271
|
+
* @example
|
|
272
|
+
* ```ts
|
|
273
|
+
* import { crypto, createRedisTransportKeyStore, parseTransportServerKeyRing } from '@h-ai/crypto'
|
|
274
|
+
* import { cache } from '@h-ai/cache'
|
|
275
|
+
* const initialized = await crypto.init()
|
|
276
|
+
* if (!initialized.success) throw new Error(initialized.error.message)
|
|
277
|
+
* try {
|
|
278
|
+
* const connected = await cache.init({ type: 'redis', host: 'cache', port: 6379, password: process.env.HAI_CACHE_PASSWORD })
|
|
279
|
+
* if (!connected.success) throw new Error(connected.error.message)
|
|
280
|
+
* try {
|
|
281
|
+
* const registry = createRedisTransportKeyStore({ cache, ttlSeconds: 3600 })
|
|
282
|
+
* const store: import('@h-ai/crypto').TransportKeyStore = registry
|
|
283
|
+
* const scope = store.stateScope
|
|
284
|
+
* // Redis部署须noeviction/可靠持久化,并同步各节点时钟。
|
|
285
|
+
* }
|
|
286
|
+
* finally { await cache.close() }
|
|
287
|
+
* }
|
|
288
|
+
* finally { await crypto.close() }
|
|
289
|
+
* ```
|
|
88
290
|
*/
|
|
89
291
|
interface TransportKeyStore {
|
|
90
|
-
/**
|
|
292
|
+
/** 公钥与nonce状态的真实共享能力;生产只接受shared。 */
|
|
293
|
+
readonly stateScope: 'local' | 'shared';
|
|
294
|
+
/**
|
|
295
|
+
* 注册有效 SM2 公钥,返回服务端生成的 clientId;格式/曲线点无效或存储故障必须 reject。
|
|
296
|
+
* 容量策略见具体工厂契约:数据库满容量拒绝,开发/测试内存实现允许 FIFO 淘汰公钥。
|
|
297
|
+
* @param publicKey - 实际调用输入,按该方法约束验证。
|
|
298
|
+
* @returns 异步操作结果;存储或网络故障按方法契约拒绝。
|
|
299
|
+
*
|
|
300
|
+
* @example
|
|
301
|
+
* ```ts
|
|
302
|
+
* import { crypto } from '@h-ai/crypto'
|
|
303
|
+
* const initialized = await crypto.init()
|
|
304
|
+
* if (!initialized.success) throw new Error(initialized.error.message)
|
|
305
|
+
* try {
|
|
306
|
+
* const generated = crypto.asymmetric.generateKeyPair()
|
|
307
|
+
* if (!generated.success) throw new Error(generated.error.message)
|
|
308
|
+
* const pair = generated.data
|
|
309
|
+
* const { createInMemoryKeyStore } = await import('@h-ai/crypto')
|
|
310
|
+
* const registry = createInMemoryKeyStore(100)
|
|
311
|
+
* try { const clientId = await registry.register(pair.publicKey) }
|
|
312
|
+
* finally { await registry.close?.() }
|
|
313
|
+
* // 本例仅开发/测试;生产替换为共享Redis/数据库registry。
|
|
314
|
+
* }
|
|
315
|
+
* finally { await crypto.close() }
|
|
316
|
+
* ```
|
|
317
|
+
*/
|
|
91
318
|
register: (publicKey: string) => Promise<string>;
|
|
92
|
-
/**
|
|
319
|
+
/**
|
|
320
|
+
* 读取已注册有效公钥;缺失返回undefined,存储失败拒绝,不能伪装未注册。
|
|
321
|
+
* @param clientId - registry生成的客户端标识。
|
|
322
|
+
* @returns 异步操作结果;存储或网络故障按方法契约拒绝。
|
|
323
|
+
*
|
|
324
|
+
* @example
|
|
325
|
+
* ```ts
|
|
326
|
+
* import { crypto } from '@h-ai/crypto'
|
|
327
|
+
* const initialized = await crypto.init()
|
|
328
|
+
* if (!initialized.success) throw new Error(initialized.error.message)
|
|
329
|
+
* try {
|
|
330
|
+
* const generated = crypto.asymmetric.generateKeyPair()
|
|
331
|
+
* if (!generated.success) throw new Error(generated.error.message)
|
|
332
|
+
* const pair = generated.data
|
|
333
|
+
* const { createInMemoryKeyStore } = await import('@h-ai/crypto')
|
|
334
|
+
* const registry = createInMemoryKeyStore(100)
|
|
335
|
+
* try { const clientId = await registry.register(pair.publicKey); const publicKey = await registry.get(clientId) }
|
|
336
|
+
* finally { await registry.close?.() }
|
|
337
|
+
* }
|
|
338
|
+
* finally { await crypto.close() }
|
|
339
|
+
* ```
|
|
340
|
+
*/
|
|
93
341
|
get: (clientId: string) => Promise<string | undefined>;
|
|
94
|
-
/**
|
|
342
|
+
/**
|
|
343
|
+
* 原子消费客户端 requestId nonce;成功返回 true,重放返回 false。
|
|
344
|
+
* 必须跨节点共享且在 expiresAt(Unix 毫秒)前不驱逐;存储故障必须 reject,禁止当作成功。
|
|
345
|
+
* @param clientId - registry生成的客户端标识。
|
|
346
|
+
* @param requestId - 本次128位随机nonce。
|
|
347
|
+
* @param expiresAt - nonce至少保留到的Unix毫秒期限。
|
|
348
|
+
* @returns 异步操作结果;存储或网络故障按方法契约拒绝。
|
|
349
|
+
*
|
|
350
|
+
* @example
|
|
351
|
+
* ```ts
|
|
352
|
+
* import { crypto } from '@h-ai/crypto'
|
|
353
|
+
* const initialized = await crypto.init()
|
|
354
|
+
* if (!initialized.success) throw new Error(initialized.error.message)
|
|
355
|
+
* try {
|
|
356
|
+
* const generated = crypto.asymmetric.generateKeyPair()
|
|
357
|
+
* if (!generated.success) throw new Error(generated.error.message)
|
|
358
|
+
* const pair = generated.data
|
|
359
|
+
* const { createInMemoryKeyStore } = await import('@h-ai/crypto')
|
|
360
|
+
* const registry = createInMemoryKeyStore(100)
|
|
361
|
+
* try {
|
|
362
|
+
* const clientId = await registry.register(pair.publicKey)
|
|
363
|
+
* const requestId = globalThis.crypto.randomUUID().replaceAll('-', '')
|
|
364
|
+
* const expiresAt = Date.now() + 360000
|
|
365
|
+
* const first = await registry.claimRequest(clientId, requestId, expiresAt)
|
|
366
|
+
* const replay = await registry.claimRequest(clientId, requestId, expiresAt)
|
|
367
|
+
* // 单进程测试预期true/false;生产需要跨节点共享原子消费且有效期间不驱逐。
|
|
368
|
+
* }
|
|
369
|
+
* finally { await registry.close?.() }
|
|
370
|
+
* }
|
|
371
|
+
* finally { await crypto.close() }
|
|
372
|
+
* ```
|
|
373
|
+
*/
|
|
374
|
+
claimRequest: (clientId: string, requestId: string, expiresAt: number) => Promise<boolean>;
|
|
375
|
+
/**
|
|
376
|
+
* 主动删除(可选,便于客户端登出时清理)。
|
|
377
|
+
* @param clientId - registry生成的客户端标识。
|
|
378
|
+
* @returns 异步操作结果;存储或网络故障按方法契约拒绝。
|
|
379
|
+
*
|
|
380
|
+
* @example
|
|
381
|
+
* ```ts
|
|
382
|
+
* import { crypto } from '@h-ai/crypto'
|
|
383
|
+
* const initialized = await crypto.init()
|
|
384
|
+
* if (!initialized.success) throw new Error(initialized.error.message)
|
|
385
|
+
* try {
|
|
386
|
+
* const generated = crypto.asymmetric.generateKeyPair()
|
|
387
|
+
* if (!generated.success) throw new Error(generated.error.message)
|
|
388
|
+
* const pair = generated.data
|
|
389
|
+
* const { createInMemoryKeyStore } = await import('@h-ai/crypto')
|
|
390
|
+
* const registry = createInMemoryKeyStore(100)
|
|
391
|
+
* try { const clientId = await registry.register(pair.publicKey); await registry.delete?.(clientId) }
|
|
392
|
+
* finally { await registry.close?.() }
|
|
393
|
+
* }
|
|
394
|
+
* finally { await crypto.close() }
|
|
395
|
+
* ```
|
|
396
|
+
*/
|
|
95
397
|
delete?: (clientId: string) => Promise<void>;
|
|
96
|
-
/**
|
|
398
|
+
/**
|
|
399
|
+
* 可选释放registry自有资源;借用的cache/reldb不由registry关闭。
|
|
400
|
+
* @returns 异步操作结果;存储或网络故障按方法契约拒绝。
|
|
401
|
+
*
|
|
402
|
+
* @example
|
|
403
|
+
* ```ts
|
|
404
|
+
* import { createInMemoryKeyStore } from '@h-ai/crypto'
|
|
405
|
+
* const registry = createInMemoryKeyStore(100)
|
|
406
|
+
* await registry.close?.()
|
|
407
|
+
* // 共享registry借用的cache/reldb仍由应用反序关闭。
|
|
408
|
+
* ```
|
|
409
|
+
*/
|
|
97
410
|
close?: () => Promise<void>;
|
|
98
411
|
}
|
|
99
412
|
/**
|
|
100
|
-
*
|
|
413
|
+
* 服务端创建配置;受管密钥环和共享registry均必填,没有进程随机密钥回退。
|
|
101
414
|
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
415
|
+
* @example
|
|
416
|
+
* ```ts
|
|
417
|
+
* import { crypto, createRedisTransportKeyStore, parseTransportServerKeyRing } from '@h-ai/crypto'
|
|
418
|
+
* import { cache } from '@h-ai/cache'
|
|
419
|
+
* const initialized = await crypto.init()
|
|
420
|
+
* if (!initialized.success) throw new Error(initialized.error.message)
|
|
421
|
+
* try {
|
|
422
|
+
* const connected = await cache.init({ type: 'redis', host: 'cache', port: 6379, password: process.env.HAI_CACHE_PASSWORD })
|
|
423
|
+
* if (!connected.success) throw new Error(connected.error.message)
|
|
424
|
+
* try {
|
|
425
|
+
* const registry = createRedisTransportKeyStore({ cache, ttlSeconds: 3600 })
|
|
426
|
+
* const parsed = parseTransportServerKeyRing(process.env.HAI_CRYPTO_TRANSPORT_SERVERKEYRING)
|
|
427
|
+
* if (!parsed.success) throw new Error(parsed.error.message)
|
|
428
|
+
* const options: import('@h-ai/crypto').TransportCreateServerOptions = { serverKeyRing: parsed.data, keyStore: registry }
|
|
429
|
+
* const created = crypto.transport.createServer(options)
|
|
430
|
+
* if (!created.success) throw new Error(created.error.message)
|
|
431
|
+
* await created.data.close()
|
|
432
|
+
* }
|
|
433
|
+
* finally { await cache.close() }
|
|
434
|
+
* }
|
|
435
|
+
* finally { await crypto.close() }
|
|
436
|
+
* ```
|
|
104
437
|
*/
|
|
105
438
|
interface TransportCreateServerOptions {
|
|
106
|
-
/**
|
|
107
|
-
readonly
|
|
108
|
-
/**
|
|
109
|
-
readonly
|
|
439
|
+
/** 必填受管服务端密钥环;没有隐式随机生成或进程密钥回退。 */
|
|
440
|
+
readonly serverKeyRing: TransportServerKeyRing;
|
|
441
|
+
/** 客户端公钥存储;生产必须注入跨节点共享实现。 */
|
|
442
|
+
readonly keyStore: TransportKeyStore;
|
|
110
443
|
}
|
|
111
444
|
/**
|
|
112
|
-
*
|
|
113
|
-
*
|
|
114
|
-
* 由 {@link createTransportEncryption} 创建;负责服务端密钥对持有、客户端密钥
|
|
115
|
-
* 注册与请求/响应的加解密。
|
|
116
|
-
*
|
|
117
|
-
* @remarks
|
|
118
|
-
* 典型调用顺序:
|
|
445
|
+
* 服务端认证传输管理器;注册公钥,按真实HTTP上下文验签/MAC/时效并消费nonce,随后才返回明文。
|
|
446
|
+
* 优先使用authenticateTransportRequest;响应只用已认证上下文。close释放registry自有资源,不关闭借用的cache/reldb。
|
|
119
447
|
*
|
|
120
|
-
*
|
|
121
|
-
*
|
|
122
|
-
*
|
|
123
|
-
*
|
|
124
|
-
*
|
|
448
|
+
* @example
|
|
449
|
+
* ```ts
|
|
450
|
+
* import { crypto, createRedisTransportKeyStore, parseTransportServerKeyRing } from '@h-ai/crypto'
|
|
451
|
+
* import { cache } from '@h-ai/cache'
|
|
452
|
+
* const initialized = await crypto.init()
|
|
453
|
+
* if (!initialized.success) throw new Error(initialized.error.message)
|
|
454
|
+
* try {
|
|
455
|
+
* const connected = await cache.init({ type: 'redis', host: 'cache', port: 6379, password: process.env.HAI_CACHE_PASSWORD })
|
|
456
|
+
* if (!connected.success) throw new Error(connected.error.message)
|
|
457
|
+
* try {
|
|
458
|
+
* const registry = createRedisTransportKeyStore({ cache, ttlSeconds: 3600 })
|
|
459
|
+
* const ring = parseTransportServerKeyRing(process.env.HAI_CRYPTO_TRANSPORT_SERVERKEYRING)
|
|
460
|
+
* if (!ring.success) throw new Error(ring.error.message)
|
|
461
|
+
* const created = crypto.transport.createServer({ serverKeyRing: ring.data, keyStore: registry })
|
|
462
|
+
* if (!created.success) throw new Error(created.error.message)
|
|
463
|
+
* const manager = created.data
|
|
464
|
+
* try {
|
|
465
|
+
* const currentVersion = manager.getServerKeyId()
|
|
466
|
+
* const publicKey = manager.getServerPublicKey()
|
|
467
|
+
* }
|
|
468
|
+
* finally { await manager.close() }
|
|
469
|
+
* }
|
|
470
|
+
* finally { await cache.close() }
|
|
471
|
+
* }
|
|
472
|
+
* finally { await crypto.close() }
|
|
473
|
+
* ```
|
|
125
474
|
*/
|
|
126
475
|
interface TransportEncryptionManager {
|
|
476
|
+
/**
|
|
477
|
+
* 当前新协商使用的服务端密钥 ID。
|
|
478
|
+
* @returns 直接返回值,使用前先初始化模块。
|
|
479
|
+
*
|
|
480
|
+
* @example
|
|
481
|
+
* ```ts
|
|
482
|
+
* import type { TransportEncryptionManager } from '@h-ai/crypto'
|
|
483
|
+
* function activeVersion(manager: TransportEncryptionManager): string { return manager.getServerKeyId() }
|
|
484
|
+
* ```
|
|
485
|
+
*/
|
|
486
|
+
getServerKeyId: () => string;
|
|
487
|
+
/**
|
|
488
|
+
* 返回当前活动版本的公开公钥,只用于可信TLS协商,不含私钥。
|
|
489
|
+
* @returns 直接返回值,使用前先初始化模块。
|
|
490
|
+
*
|
|
491
|
+
* @example
|
|
492
|
+
* ```ts
|
|
493
|
+
* import type { TransportEncryptionManager } from '@h-ai/crypto'
|
|
494
|
+
* function activePublicKey(manager: TransportEncryptionManager): string { return manager.getServerPublicKey() }
|
|
495
|
+
* ```
|
|
496
|
+
*/
|
|
127
497
|
getServerPublicKey: () => string;
|
|
498
|
+
/**
|
|
499
|
+
* 验证 SM2 公钥编码和曲线点后向显式 registry 注册。
|
|
500
|
+
* @throws 公钥无效或 registry 写入失败;HTTP 协商适配器负责转换成失败响应。
|
|
501
|
+
* @param clientPublicKey - 实际调用输入,按该方法约束验证。
|
|
502
|
+
* @returns 异步操作结果;存储或网络故障按方法契约拒绝。
|
|
503
|
+
*
|
|
504
|
+
* @example
|
|
505
|
+
* ```ts
|
|
506
|
+
* import type { TransportEncryptionManager } from '@h-ai/crypto'
|
|
507
|
+
* async function register(manager: TransportEncryptionManager, clientPublicKey: string): Promise<string> {
|
|
508
|
+
* // 方法校验编码/曲线点后写入共享registry;异常由HTTP协商适配器转成失败响应。
|
|
509
|
+
* return manager.registerClientKey(clientPublicKey)
|
|
510
|
+
* }
|
|
511
|
+
* ```
|
|
512
|
+
*/
|
|
128
513
|
registerClientKey: (clientPublicKey: string) => Promise<string>;
|
|
514
|
+
/**
|
|
515
|
+
* 查询客户端公钥;registry 读取故障会 reject,不作为未注册处理。
|
|
516
|
+
* @param clientId - registry生成的客户端标识。
|
|
517
|
+
* @returns 异步操作结果;存储或网络故障按方法契约拒绝。
|
|
518
|
+
*
|
|
519
|
+
* @example
|
|
520
|
+
* ```ts
|
|
521
|
+
* import type { TransportEncryptionManager } from '@h-ai/crypto'
|
|
522
|
+
* async function lookup(manager: TransportEncryptionManager, clientId: string): Promise<string | undefined> {
|
|
523
|
+
* return manager.getClientPublicKey(clientId)
|
|
524
|
+
* }
|
|
525
|
+
* ```
|
|
526
|
+
*/
|
|
129
527
|
getClientPublicKey: (clientId: string) => Promise<string | undefined>;
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
528
|
+
/**
|
|
529
|
+
* 按已认证请求上下文加密、MAC 并签名响应,绑定实际 HTTP 状态。
|
|
530
|
+
* registry 及加密故障均返回失败 HaiResult;上下文只能来自成功的请求认证。
|
|
531
|
+
* @param request - 实际调用输入,按该方法约束验证。
|
|
532
|
+
* @param data - 实际调用输入,按该方法约束验证。
|
|
533
|
+
* @param status - 实际调用输入,按该方法约束验证。
|
|
534
|
+
* @returns HaiResult,先检查success再读取data。
|
|
535
|
+
*
|
|
536
|
+
* @example
|
|
537
|
+
* ```ts
|
|
538
|
+
* import { authenticateTransportRequest, type EncryptedPayload, type TransportEncryptionManager } from '@h-ai/crypto'
|
|
539
|
+
* async function sealResponse(manager: TransportEncryptionManager, request: Request): Promise<EncryptedPayload> {
|
|
540
|
+
* const verified = await authenticateTransportRequest(manager, request)
|
|
541
|
+
* if (!verified.success) throw new Error(verified.error.message)
|
|
542
|
+
* const encrypted = await manager.encryptResponse(verified.data.context, JSON.stringify({ accepted: true }), 200)
|
|
543
|
+
* if (!encrypted.success) throw new Error(encrypted.error.message)
|
|
544
|
+
* return encrypted.data
|
|
545
|
+
* }
|
|
546
|
+
* ```
|
|
547
|
+
*/
|
|
548
|
+
encryptResponse: (request: TransportMessageContext, data: string, status: number) => Promise<HaiResult<EncryptedPayload>>;
|
|
549
|
+
/**
|
|
550
|
+
* 验证发送方、MAC、HTTP 绑定和时效,原子消费 nonce 后才返回明文;错误不含密文或 Secret。
|
|
551
|
+
* expected 须从实际 Request 提取,不从 payload 复制。HTTP 优先用 authenticateTransportRequest。
|
|
552
|
+
* @param payload - 实际调用输入,按该方法约束验证。
|
|
553
|
+
* @param expected - 实际调用输入,按该方法约束验证。
|
|
554
|
+
* @returns HaiResult,先检查success再读取data。
|
|
555
|
+
*
|
|
556
|
+
* @example
|
|
557
|
+
* ```ts
|
|
558
|
+
* import type { EncryptedRequestPayload, TransportEncryptionManager } from '@h-ai/crypto'
|
|
559
|
+
* async function decrypt(manager: TransportEncryptionManager, request: Request, payload: EncryptedRequestPayload): Promise<string> {
|
|
560
|
+
* const clientId = request.headers.get('X-Client-Id')
|
|
561
|
+
* if (!clientId) throw new Error('Missing client ID')
|
|
562
|
+
* const url = new URL(request.url)
|
|
563
|
+
* const result = await manager.decryptRequest(payload, { clientId, method: request.method, path: url.pathname + url.search })
|
|
564
|
+
* if (!result.success) throw new Error(result.error.message)
|
|
565
|
+
* return result.data
|
|
566
|
+
* }
|
|
567
|
+
* ```
|
|
568
|
+
*/
|
|
569
|
+
decryptRequest: (payload: EncryptedRequestPayload, expected: TransportRequestContext) => Promise<HaiResult<string>>;
|
|
570
|
+
/**
|
|
571
|
+
* 释放registry自有资源;应用仍负责借用cache/reldb的关闭。
|
|
572
|
+
* @returns 异步操作结果;存储或网络故障按方法契约拒绝。
|
|
573
|
+
*
|
|
574
|
+
* @example
|
|
575
|
+
* ```ts
|
|
576
|
+
* import { crypto, createRedisTransportKeyStore, parseTransportServerKeyRing } from '@h-ai/crypto'
|
|
577
|
+
* import { cache } from '@h-ai/cache'
|
|
578
|
+
* const initialized = await crypto.init()
|
|
579
|
+
* if (!initialized.success) throw new Error(initialized.error.message)
|
|
580
|
+
* try {
|
|
581
|
+
* const connected = await cache.init({ type: 'redis', host: 'cache', port: 6379, password: process.env.HAI_CACHE_PASSWORD })
|
|
582
|
+
* if (!connected.success) throw new Error(connected.error.message)
|
|
583
|
+
* try {
|
|
584
|
+
* const registry = createRedisTransportKeyStore({ cache, ttlSeconds: 3600 })
|
|
585
|
+
* const ring = parseTransportServerKeyRing(process.env.HAI_CRYPTO_TRANSPORT_SERVERKEYRING)
|
|
586
|
+
* if (!ring.success) throw new Error(ring.error.message)
|
|
587
|
+
* const created = crypto.transport.createServer({ serverKeyRing: ring.data, keyStore: registry })
|
|
588
|
+
* if (!created.success) throw new Error(created.error.message)
|
|
589
|
+
* const manager = created.data
|
|
590
|
+
* try {
|
|
591
|
+
* const currentVersion = manager.getServerKeyId()
|
|
592
|
+
* const publicKey = manager.getServerPublicKey()
|
|
593
|
+
* }
|
|
594
|
+
* finally { await manager.close() }
|
|
595
|
+
* }
|
|
596
|
+
* finally { await cache.close() }
|
|
597
|
+
* }
|
|
598
|
+
* finally { await crypto.close() }
|
|
599
|
+
* ```
|
|
600
|
+
*/
|
|
133
601
|
close: () => Promise<void>;
|
|
134
602
|
}
|
|
135
603
|
/**
|
|
136
|
-
*
|
|
604
|
+
* 客户端单会话;首次调用自动协商,后续请求生成随机nonce并认证响应关联/签名/MAC。
|
|
605
|
+
* init返回HaiResult;encryptedFetch按fetch契约reject;destroy清本地会话,不代表撤销服务端registry。
|
|
137
606
|
*
|
|
138
|
-
*
|
|
139
|
-
*
|
|
140
|
-
*
|
|
141
|
-
*
|
|
142
|
-
*
|
|
143
|
-
*
|
|
144
|
-
*
|
|
145
|
-
*
|
|
146
|
-
*
|
|
147
|
-
*
|
|
607
|
+
* @example
|
|
608
|
+
* ```ts
|
|
609
|
+
* import { crypto } from '@h-ai/crypto'
|
|
610
|
+
* const initialized = await crypto.init()
|
|
611
|
+
* if (!initialized.success) throw new Error(initialized.error.message)
|
|
612
|
+
* try {
|
|
613
|
+
* const client = crypto.transport.createClient({ keyExchangeUrl: 'https://api.example.com/api/v1/_hai/key-exchange' })
|
|
614
|
+
* try {
|
|
615
|
+
* const ready = await client.init()
|
|
616
|
+
* if (!ready.success) throw new Error(ready.error.message)
|
|
617
|
+
* const response = await client.encryptedFetch('https://api.example.com/api/v1/orders')
|
|
618
|
+
* if (!response.ok) throw new Error('HTTP request failed')
|
|
619
|
+
* }
|
|
620
|
+
* finally { client.destroy() }
|
|
621
|
+
* }
|
|
622
|
+
* finally { await crypto.close() }
|
|
623
|
+
* ```
|
|
148
624
|
*/
|
|
149
625
|
interface TransportClient {
|
|
150
626
|
/**
|
|
151
627
|
* 完成密钥协商;多次调用幂等(同一会话只协商一次)。
|
|
152
628
|
*
|
|
153
629
|
* 通常无需手动调用:`encryptedFetch` 在首次请求前会自动 `init()`。
|
|
630
|
+
* 协商在 30 秒内完成,握手 JSON 与单个公钥分别受 TRANSPORT_BODY_LIMITS 限制。
|
|
631
|
+
* @returns HaiResult,先检查success再读取data。
|
|
632
|
+
*
|
|
633
|
+
* @example
|
|
634
|
+
* ```ts
|
|
635
|
+
* import { crypto } from '@h-ai/crypto'
|
|
636
|
+
* const initialized = await crypto.init()
|
|
637
|
+
* if (!initialized.success) throw new Error(initialized.error.message)
|
|
638
|
+
* try {
|
|
639
|
+
* const client = crypto.transport.createClient({ keyExchangeUrl: 'https://api.example.com/api/v1/_hai/key-exchange' })
|
|
640
|
+
* try {
|
|
641
|
+
* const ready = await client.init()
|
|
642
|
+
* if (!ready.success) throw new Error(ready.error.message)
|
|
643
|
+
* const response = await client.encryptedFetch('https://api.example.com/api/v1/orders')
|
|
644
|
+
* if (!response.ok) throw new Error('HTTP request failed')
|
|
645
|
+
* }
|
|
646
|
+
* finally { client.destroy() }
|
|
647
|
+
* }
|
|
648
|
+
* finally { await crypto.close() }
|
|
649
|
+
* ```
|
|
154
650
|
*/
|
|
155
651
|
init: () => Promise<HaiResult<void>>;
|
|
156
652
|
/**
|
|
157
653
|
* 包装后的 fetch;签名与全局 `fetch` 一致,请求/响应自动加解密。
|
|
654
|
+
* 明文/密文按 TRANSPORT_BODY_LIMITS 有界读取原流;超限或取消释放 reader,保留调用方 signal。
|
|
655
|
+
* 仅接受关联同一请求上下文与 HTTP status 的认证响应;缺标记也拒绝,不回退明文或密文。
|
|
158
656
|
*
|
|
159
657
|
* @throws 与原生 fetch 一样,网络错误、密钥协商失败、加密失败或解密失败会 reject。
|
|
658
|
+
*
|
|
659
|
+
* @example
|
|
660
|
+
* ```ts
|
|
661
|
+
* import { crypto } from '@h-ai/crypto'
|
|
662
|
+
* const initialized = await crypto.init()
|
|
663
|
+
* if (!initialized.success) throw new Error(initialized.error.message)
|
|
664
|
+
* try {
|
|
665
|
+
* const client = crypto.transport.createClient({ keyExchangeUrl: 'https://api.example.com/api/v1/_hai/key-exchange' })
|
|
666
|
+
* try {
|
|
667
|
+
* const ready = await client.init()
|
|
668
|
+
* if (!ready.success) throw new Error(ready.error.message)
|
|
669
|
+
* const response = await client.encryptedFetch('https://api.example.com/api/v1/orders')
|
|
670
|
+
* if (!response.ok) throw new Error('HTTP request failed')
|
|
671
|
+
* }
|
|
672
|
+
* finally { client.destroy() }
|
|
673
|
+
* }
|
|
674
|
+
* finally { await crypto.close() }
|
|
675
|
+
* ```
|
|
160
676
|
*/
|
|
161
677
|
encryptedFetch: typeof fetch;
|
|
162
|
-
/**
|
|
678
|
+
/**
|
|
679
|
+
* 是否已完成密钥协商。
|
|
680
|
+
* @returns 直接boolean,false表示不匹配/未就绪。
|
|
681
|
+
*
|
|
682
|
+
* @example
|
|
683
|
+
* ```ts
|
|
684
|
+
* import { crypto } from '@h-ai/crypto'
|
|
685
|
+
* const initialized = await crypto.init()
|
|
686
|
+
* if (!initialized.success) throw new Error(initialized.error.message)
|
|
687
|
+
* try {
|
|
688
|
+
* const client = crypto.transport.createClient({ keyExchangeUrl: 'https://api.example.com/api/v1/_hai/key-exchange' })
|
|
689
|
+
* try { const ready = client.ready() }
|
|
690
|
+
* finally { client.destroy() }
|
|
691
|
+
* }
|
|
692
|
+
* finally { await crypto.close() }
|
|
693
|
+
* ```
|
|
694
|
+
*/
|
|
163
695
|
ready: () => boolean;
|
|
164
|
-
/**
|
|
696
|
+
/**
|
|
697
|
+
* 清空本地客户端密钥与协商状态;下次请求重新协商,不撤销服务端身份或业务效果。
|
|
698
|
+
* @returns 直接返回值,使用前先初始化模块。
|
|
699
|
+
*
|
|
700
|
+
* @example
|
|
701
|
+
* ```ts
|
|
702
|
+
* import { crypto } from '@h-ai/crypto'
|
|
703
|
+
* const initialized = await crypto.init()
|
|
704
|
+
* if (!initialized.success) throw new Error(initialized.error.message)
|
|
705
|
+
* try {
|
|
706
|
+
* const client = crypto.transport.createClient({ keyExchangeUrl: 'https://api.example.com/api/v1/_hai/key-exchange' })
|
|
707
|
+
* client.destroy()
|
|
708
|
+
* // 只清本地会话;不撤销服务端registry或业务会话。
|
|
709
|
+
* }
|
|
710
|
+
* finally { await crypto.close() }
|
|
711
|
+
* ```
|
|
712
|
+
*/
|
|
165
713
|
destroy: () => void;
|
|
166
714
|
}
|
|
167
715
|
|
|
716
|
+
/**
|
|
717
|
+
* 加密领域标准错误集合;先判断 HaiResult.success,错误上下文不得附带明文或私钥。
|
|
718
|
+
*
|
|
719
|
+
* @example
|
|
720
|
+
* ```ts
|
|
721
|
+
* import { crypto, HaiCryptoError } from '@h-ai/crypto'
|
|
722
|
+
* const result = crypto.hash.hash('hello')
|
|
723
|
+
* if (!result.success && result.error.code === HaiCryptoError.NOT_INITIALIZED.code) {
|
|
724
|
+
* // 尚未初始化时按生命周期处理,不忽略错误继续加密。
|
|
725
|
+
* const message = result.error.message
|
|
726
|
+
* }
|
|
727
|
+
* ```
|
|
728
|
+
*/
|
|
168
729
|
declare const HaiCryptoError: {
|
|
169
730
|
readonly INVALID_INPUT: _h_ai_core.HaiErrorDef;
|
|
170
731
|
readonly INVALID_KEY: _h_ai_core.HaiErrorDef;
|
|
@@ -179,39 +740,182 @@ declare const HaiCryptoError: {
|
|
|
179
740
|
readonly HMAC_FAILED: _h_ai_core.HaiErrorDef;
|
|
180
741
|
readonly INVALID_IV: _h_ai_core.HaiErrorDef;
|
|
181
742
|
};
|
|
182
|
-
/**
|
|
743
|
+
/**
|
|
744
|
+
* SM2 密文排列:1为C1C3C2默认值,0为显式C1C2C3;解密必须匹配加密排列。
|
|
745
|
+
*
|
|
746
|
+
* @example
|
|
747
|
+
* ```ts
|
|
748
|
+
* import { crypto, type CipherMode } from '@h-ai/crypto'
|
|
749
|
+
* const initialized = await crypto.init()
|
|
750
|
+
* if (!initialized.success) throw new Error(initialized.error.message)
|
|
751
|
+
* try {
|
|
752
|
+
* const generated = crypto.asymmetric.generateKeyPair()
|
|
753
|
+
* if (!generated.success) throw new Error(generated.error.message)
|
|
754
|
+
* const pair = generated.data
|
|
755
|
+
* const mode: CipherMode = 1
|
|
756
|
+
* const encrypted = crypto.asymmetric.encrypt('hello', pair.publicKey, { cipherMode: mode })
|
|
757
|
+
* if (!encrypted.success) throw new Error(encrypted.error.message)
|
|
758
|
+
* const decrypted = crypto.asymmetric.decrypt(encrypted.data, pair.privateKey, { cipherMode: mode })
|
|
759
|
+
* if (!decrypted.success) throw new Error(decrypted.error.message)
|
|
760
|
+
* }
|
|
761
|
+
* finally { await crypto.close() }
|
|
762
|
+
* ```
|
|
763
|
+
*/
|
|
183
764
|
type CipherMode = 0 | 1;
|
|
184
|
-
/**
|
|
765
|
+
/**
|
|
766
|
+
* SM2 公私钥对;私钥必须保密,不写日志、公开配置或响应。
|
|
767
|
+
*
|
|
768
|
+
* @example
|
|
769
|
+
* ```ts
|
|
770
|
+
* import { crypto, type KeyPair } from '@h-ai/crypto'
|
|
771
|
+
* const initialized = await crypto.init()
|
|
772
|
+
* if (!initialized.success) throw new Error(initialized.error.message)
|
|
773
|
+
* try {
|
|
774
|
+
* const generated = crypto.asymmetric.generateKeyPair()
|
|
775
|
+
* if (!generated.success) throw new Error(generated.error.message)
|
|
776
|
+
* const pair = generated.data
|
|
777
|
+
* const keyPair: KeyPair = pair
|
|
778
|
+
* const publicKey = keyPair.publicKey
|
|
779
|
+
* }
|
|
780
|
+
* finally { await crypto.close() }
|
|
781
|
+
* ```
|
|
782
|
+
*/
|
|
185
783
|
interface KeyPair {
|
|
186
784
|
/** 公钥(十六进制字符串,包含 04 前缀为非压缩格式) */
|
|
187
785
|
publicKey: string;
|
|
188
786
|
/** 私钥(十六进制字符串,64 字符) */
|
|
189
787
|
privateKey: string;
|
|
190
788
|
}
|
|
191
|
-
/**
|
|
789
|
+
/**
|
|
790
|
+
* SM2 密文排列和输出编码;加解密 cipherMode 必须一致,解密可识别受支持的 base64 输出。
|
|
791
|
+
*
|
|
792
|
+
* @example
|
|
793
|
+
* ```ts
|
|
794
|
+
* import { crypto, type AsymmetricEncryptOptions } from '@h-ai/crypto'
|
|
795
|
+
* const initialized = await crypto.init()
|
|
796
|
+
* if (!initialized.success) throw new Error(initialized.error.message)
|
|
797
|
+
* try {
|
|
798
|
+
* const generated = crypto.asymmetric.generateKeyPair()
|
|
799
|
+
* if (!generated.success) throw new Error(generated.error.message)
|
|
800
|
+
* const pair = generated.data
|
|
801
|
+
* const options: AsymmetricEncryptOptions = { cipherMode: 1, outputFormat: 'base64' }
|
|
802
|
+
* const encrypted = crypto.asymmetric.encrypt('hello', pair.publicKey, options)
|
|
803
|
+
* if (!encrypted.success) throw new Error(encrypted.error.message)
|
|
804
|
+
* const decrypted = crypto.asymmetric.decrypt(encrypted.data, pair.privateKey, options)
|
|
805
|
+
* if (!decrypted.success) throw new Error(decrypted.error.message)
|
|
806
|
+
* }
|
|
807
|
+
* finally { await crypto.close() }
|
|
808
|
+
* ```
|
|
809
|
+
*/
|
|
192
810
|
interface AsymmetricEncryptOptions {
|
|
193
811
|
/** 密文模式:0=C1C2C3(旧版),1=C1C3C2(国标,默认) */
|
|
194
812
|
cipherMode?: CipherMode;
|
|
195
813
|
/** 输出格式 */
|
|
196
814
|
outputFormat?: 'hex' | 'base64';
|
|
197
815
|
}
|
|
198
|
-
/**
|
|
816
|
+
/**
|
|
817
|
+
* SM2 签名的哈希与用户标识参数;验签必须使用相同设置,默认启用哈希。
|
|
818
|
+
*
|
|
819
|
+
* @example
|
|
820
|
+
* ```ts
|
|
821
|
+
* import { crypto, type SignOptions } from '@h-ai/crypto'
|
|
822
|
+
* const initialized = await crypto.init()
|
|
823
|
+
* if (!initialized.success) throw new Error(initialized.error.message)
|
|
824
|
+
* try {
|
|
825
|
+
* const generated = crypto.asymmetric.generateKeyPair()
|
|
826
|
+
* if (!generated.success) throw new Error(generated.error.message)
|
|
827
|
+
* const pair = generated.data
|
|
828
|
+
* const options: SignOptions = { hash: true, userId: 'orders-service' }
|
|
829
|
+
* const signature = crypto.asymmetric.sign('hello', pair.privateKey, options)
|
|
830
|
+
* if (!signature.success) throw new Error(signature.error.message)
|
|
831
|
+
* const verified = crypto.asymmetric.verify('hello', signature.data, pair.publicKey, options)
|
|
832
|
+
* if (!verified.success) throw new Error(verified.error.message)
|
|
833
|
+
* const accepted = verified.data
|
|
834
|
+
* }
|
|
835
|
+
* finally { await crypto.close() }
|
|
836
|
+
* ```
|
|
837
|
+
*/
|
|
199
838
|
interface SignOptions {
|
|
200
839
|
/** 是否对数据进行哈希(默认 true) */
|
|
201
840
|
hash?: boolean;
|
|
202
841
|
/** 用户 ID(默认 "1234567812345678") */
|
|
203
842
|
userId?: string;
|
|
204
843
|
}
|
|
205
|
-
/**
|
|
844
|
+
/**
|
|
845
|
+
* 字符串输入编码;hex 表示解码后的字节,Uint8Array 直接作为字节输入。
|
|
846
|
+
*
|
|
847
|
+
* @example
|
|
848
|
+
* ```ts
|
|
849
|
+
* import { crypto, type HashOptions } from '@h-ai/crypto'
|
|
850
|
+
* const initialized = await crypto.init()
|
|
851
|
+
* if (!initialized.success) throw new Error(initialized.error.message)
|
|
852
|
+
* try {
|
|
853
|
+
* const options: HashOptions = { inputEncoding: 'hex' }
|
|
854
|
+
* const digest = crypto.hash.hash('6869', options)
|
|
855
|
+
* if (!digest.success) throw new Error(digest.error.message)
|
|
856
|
+
* }
|
|
857
|
+
* finally { await crypto.close() }
|
|
858
|
+
* ```
|
|
859
|
+
*/
|
|
206
860
|
interface HashOptions {
|
|
207
861
|
/** 输入编码 */
|
|
208
862
|
inputEncoding?: 'utf8' | 'hex';
|
|
209
863
|
}
|
|
210
|
-
/**
|
|
864
|
+
/**
|
|
865
|
+
* SM4 的 CBC/ECB 模式;新数据使用默认 CBC,ECB 会暴露相同明文块结构,两者都不提供 MAC。
|
|
866
|
+
*
|
|
867
|
+
* @example
|
|
868
|
+
* ```ts
|
|
869
|
+
* import { crypto, type SymmetricMode } from '@h-ai/crypto'
|
|
870
|
+
* const initialized = await crypto.init()
|
|
871
|
+
* if (!initialized.success) throw new Error(initialized.error.message)
|
|
872
|
+
* try {
|
|
873
|
+
* const mode: SymmetricMode = 'cbc'
|
|
874
|
+
* const key = crypto.symmetric.generateKey()
|
|
875
|
+
* const encrypted = crypto.symmetric.encrypt('hello', key, { mode })
|
|
876
|
+
* if (!encrypted.success) throw new Error(encrypted.error.message)
|
|
877
|
+
* }
|
|
878
|
+
* finally { await crypto.close() }
|
|
879
|
+
* ```
|
|
880
|
+
*/
|
|
211
881
|
type SymmetricMode = 'ecb' | 'cbc';
|
|
212
|
-
/**
|
|
882
|
+
/**
|
|
883
|
+
* 结构化 SM4 ciphertext 的 hex/base64 编码标识,解密必须保留。
|
|
884
|
+
*
|
|
885
|
+
* @example
|
|
886
|
+
* ```ts
|
|
887
|
+
* import { crypto, type SymmetricCiphertextEncoding } from '@h-ai/crypto'
|
|
888
|
+
* const initialized = await crypto.init()
|
|
889
|
+
* if (!initialized.success) throw new Error(initialized.error.message)
|
|
890
|
+
* try {
|
|
891
|
+
* const outputFormat: SymmetricCiphertextEncoding = 'base64'
|
|
892
|
+
* const key = crypto.symmetric.generateKey()
|
|
893
|
+
* const encrypted = crypto.symmetric.encrypt('hello', key, { outputFormat })
|
|
894
|
+
* if (!encrypted.success) throw new Error(encrypted.error.message)
|
|
895
|
+
* const decrypted = crypto.symmetric.decrypt(encrypted.data, key)
|
|
896
|
+
* if (!decrypted.success) throw new Error(decrypted.error.message)
|
|
897
|
+
* }
|
|
898
|
+
* finally { await crypto.close() }
|
|
899
|
+
* ```
|
|
900
|
+
*/
|
|
213
901
|
type SymmetricCiphertextEncoding = 'hex' | 'base64';
|
|
214
|
-
/**
|
|
902
|
+
/**
|
|
903
|
+
* SM4 模式、IV 与编码;CBC 未提供 IV 时随机生成,手动 IV 必须每次独立随机。
|
|
904
|
+
*
|
|
905
|
+
* @example
|
|
906
|
+
* ```ts
|
|
907
|
+
* import { crypto, type SymmetricEncryptOptions } from '@h-ai/crypto'
|
|
908
|
+
* const initialized = await crypto.init()
|
|
909
|
+
* if (!initialized.success) throw new Error(initialized.error.message)
|
|
910
|
+
* try {
|
|
911
|
+
* const options: SymmetricEncryptOptions = { mode: 'cbc', outputFormat: 'base64' }
|
|
912
|
+
* const key = crypto.symmetric.generateKey()
|
|
913
|
+
* const encrypted = crypto.symmetric.encrypt('hello', key, options)
|
|
914
|
+
* if (!encrypted.success) throw new Error(encrypted.error.message)
|
|
915
|
+
* }
|
|
916
|
+
* finally { await crypto.close() }
|
|
917
|
+
* ```
|
|
918
|
+
*/
|
|
215
919
|
interface SymmetricEncryptOptions {
|
|
216
920
|
/** 加密模式;默认 cbc */
|
|
217
921
|
mode?: SymmetricMode;
|
|
@@ -220,7 +924,25 @@ interface SymmetricEncryptOptions {
|
|
|
220
924
|
/** 密文输出编码;默认 hex */
|
|
221
925
|
outputFormat?: SymmetricCiphertextEncoding;
|
|
222
926
|
}
|
|
223
|
-
/**
|
|
927
|
+
/**
|
|
928
|
+
* SM4 密文及 mode/encoding/可选 IV;保留全部字段才能正确解密,不含消息认证能力。
|
|
929
|
+
*
|
|
930
|
+
* @example
|
|
931
|
+
* ```ts
|
|
932
|
+
* import { crypto, type SymmetricEncryptedPayload } from '@h-ai/crypto'
|
|
933
|
+
* const initialized = await crypto.init()
|
|
934
|
+
* if (!initialized.success) throw new Error(initialized.error.message)
|
|
935
|
+
* try {
|
|
936
|
+
* const key = crypto.symmetric.generateKey()
|
|
937
|
+
* const encrypted = crypto.symmetric.encrypt('hello', key)
|
|
938
|
+
* if (!encrypted.success) throw new Error(encrypted.error.message)
|
|
939
|
+
* const payload: SymmetricEncryptedPayload = encrypted.data
|
|
940
|
+
* const decrypted = crypto.symmetric.decrypt(payload, key)
|
|
941
|
+
* if (!decrypted.success) throw new Error(decrypted.error.message)
|
|
942
|
+
* }
|
|
943
|
+
* finally { await crypto.close() }
|
|
944
|
+
* ```
|
|
945
|
+
*/
|
|
224
946
|
interface SymmetricEncryptedPayload {
|
|
225
947
|
/** 加密模式 */
|
|
226
948
|
readonly mode: SymmetricMode;
|
|
@@ -231,16 +953,87 @@ interface SymmetricEncryptedPayload {
|
|
|
231
953
|
/** ciphertext 字段的编码 */
|
|
232
954
|
readonly encoding: SymmetricCiphertextEncoding;
|
|
233
955
|
}
|
|
234
|
-
/**
|
|
956
|
+
/**
|
|
957
|
+
* CBC 结构化密文,mode固定cbc且IV必填;仍需上层消息认证。
|
|
958
|
+
*
|
|
959
|
+
* @example
|
|
960
|
+
* ```ts
|
|
961
|
+
* import { crypto, type SymmetricCbcEncryptedPayload } from '@h-ai/crypto'
|
|
962
|
+
* const initialized = await crypto.init()
|
|
963
|
+
* if (!initialized.success) throw new Error(initialized.error.message)
|
|
964
|
+
* try {
|
|
965
|
+
* const key = crypto.symmetric.generateKey()
|
|
966
|
+
* const encrypted = crypto.symmetric.encryptWithIV('hello', key)
|
|
967
|
+
* if (!encrypted.success) throw new Error(encrypted.error.message)
|
|
968
|
+
* const payload: SymmetricCbcEncryptedPayload = encrypted.data
|
|
969
|
+
* const decrypted = crypto.symmetric.decrypt(payload, key)
|
|
970
|
+
* if (!decrypted.success) throw new Error(decrypted.error.message)
|
|
971
|
+
* }
|
|
972
|
+
* finally { await crypto.close() }
|
|
973
|
+
* ```
|
|
974
|
+
*/
|
|
235
975
|
interface SymmetricCbcEncryptedPayload extends SymmetricEncryptedPayload {
|
|
236
976
|
readonly mode: 'cbc';
|
|
237
977
|
readonly iv: string;
|
|
238
978
|
}
|
|
239
|
-
/**
|
|
979
|
+
/**
|
|
980
|
+
* SM4 解密输入别名;传入加密返回的完整结构,不能用裸密文猜测IV或编码。
|
|
981
|
+
*
|
|
982
|
+
* @example
|
|
983
|
+
* ```ts
|
|
984
|
+
* import { crypto, type SymmetricDecryptInput } from '@h-ai/crypto'
|
|
985
|
+
* const initialized = await crypto.init()
|
|
986
|
+
* if (!initialized.success) throw new Error(initialized.error.message)
|
|
987
|
+
* try {
|
|
988
|
+
* const key = crypto.symmetric.generateKey()
|
|
989
|
+
* const encrypted = crypto.symmetric.encrypt('hello', key)
|
|
990
|
+
* if (!encrypted.success) throw new Error(encrypted.error.message)
|
|
991
|
+
* const payload: SymmetricDecryptInput = encrypted.data
|
|
992
|
+
* const decrypted = crypto.symmetric.decrypt(payload, key)
|
|
993
|
+
* if (!decrypted.success) throw new Error(decrypted.error.message)
|
|
994
|
+
* }
|
|
995
|
+
* finally { await crypto.close() }
|
|
996
|
+
* ```
|
|
997
|
+
*/
|
|
240
998
|
type SymmetricDecryptInput = SymmetricEncryptedPayload;
|
|
241
|
-
/**
|
|
999
|
+
/**
|
|
1000
|
+
* encryptWithIV 的CBC结构化结果别名;含 mode、ciphertext、iv 与 encoding。
|
|
1001
|
+
*
|
|
1002
|
+
* @example
|
|
1003
|
+
* ```ts
|
|
1004
|
+
* import { crypto, type EncryptWithIVResult } from '@h-ai/crypto'
|
|
1005
|
+
* const initialized = await crypto.init()
|
|
1006
|
+
* if (!initialized.success) throw new Error(initialized.error.message)
|
|
1007
|
+
* try {
|
|
1008
|
+
* const key = crypto.symmetric.generateKey()
|
|
1009
|
+
* const encrypted = crypto.symmetric.encryptWithIV('hello', key)
|
|
1010
|
+
* if (!encrypted.success) throw new Error(encrypted.error.message)
|
|
1011
|
+
* const payload: EncryptWithIVResult = encrypted.data
|
|
1012
|
+
* const decrypted = crypto.symmetric.decrypt(payload, key)
|
|
1013
|
+
* if (!decrypted.success) throw new Error(decrypted.error.message)
|
|
1014
|
+
* }
|
|
1015
|
+
* finally { await crypto.close() }
|
|
1016
|
+
* ```
|
|
1017
|
+
*/
|
|
242
1018
|
type EncryptWithIVResult = SymmetricCbcEncryptedPayload;
|
|
243
|
-
/**
|
|
1019
|
+
/**
|
|
1020
|
+
* 迭代SM3密码参数;默认盐长16、迭代10000,合法范围以实现校验为准,已有哈希自行携带参数。
|
|
1021
|
+
*
|
|
1022
|
+
* @example
|
|
1023
|
+
* ```ts
|
|
1024
|
+
* import { crypto, type PasswordConfig } from '@h-ai/crypto'
|
|
1025
|
+
* const initialized = await crypto.init()
|
|
1026
|
+
* if (!initialized.success) throw new Error(initialized.error.message)
|
|
1027
|
+
* try {
|
|
1028
|
+
* const config: PasswordConfig = { saltLength: 16, iterations: 10000 }
|
|
1029
|
+
* const hashed = crypto.password.hash('test-password', config)
|
|
1030
|
+
* if (!hashed.success) throw new Error(hashed.error.message)
|
|
1031
|
+
* const verified = crypto.password.verify('test-password', hashed.data)
|
|
1032
|
+
* if (!verified.success) throw new Error(verified.error.message)
|
|
1033
|
+
* }
|
|
1034
|
+
* finally { await crypto.close() }
|
|
1035
|
+
* ```
|
|
1036
|
+
*/
|
|
244
1037
|
interface PasswordConfig {
|
|
245
1038
|
/** 盐值长度(默认 16) */
|
|
246
1039
|
saltLength?: number;
|
|
@@ -248,15 +1041,47 @@ interface PasswordConfig {
|
|
|
248
1041
|
iterations?: number;
|
|
249
1042
|
}
|
|
250
1043
|
/**
|
|
251
|
-
*
|
|
1044
|
+
* SM2 操作域;先初始化,所有 HaiResult 先检查 success,boolean 校验器按直接返回值使用。
|
|
1045
|
+
* 公钥曲线点有效不等于对端身份可信;可信公钥分发仍由TLS/受管信任链保证。
|
|
252
1046
|
*
|
|
253
|
-
*
|
|
1047
|
+
* @example
|
|
1048
|
+
* ```ts
|
|
1049
|
+
* import { crypto, type AsymmetricOperations } from '@h-ai/crypto'
|
|
1050
|
+
* const initialized = await crypto.init()
|
|
1051
|
+
* if (!initialized.success) throw new Error(initialized.error.message)
|
|
1052
|
+
* try {
|
|
1053
|
+
* const generated = crypto.asymmetric.generateKeyPair()
|
|
1054
|
+
* if (!generated.success) throw new Error(generated.error.message)
|
|
1055
|
+
* const pair = generated.data
|
|
1056
|
+
* const operations: AsymmetricOperations = crypto.asymmetric
|
|
1057
|
+
* const signature = operations.sign('hello', pair.privateKey)
|
|
1058
|
+
* if (!signature.success) throw new Error(signature.error.message)
|
|
1059
|
+
* const verified = operations.verify('hello', signature.data, pair.publicKey)
|
|
1060
|
+
* if (!verified.success) throw new Error(verified.error.message)
|
|
1061
|
+
* const matches = verified.data
|
|
1062
|
+
* }
|
|
1063
|
+
* finally { await crypto.close() }
|
|
1064
|
+
* ```
|
|
254
1065
|
*/
|
|
255
1066
|
interface AsymmetricOperations {
|
|
256
1067
|
/**
|
|
257
1068
|
* 生成密钥对
|
|
258
1069
|
*
|
|
259
1070
|
* @returns 成功时包含公私钥对;失败时返回 KEY_GENERATION_FAILED
|
|
1071
|
+
*
|
|
1072
|
+
* @example
|
|
1073
|
+
* ```ts
|
|
1074
|
+
* import { crypto } from '@h-ai/crypto'
|
|
1075
|
+
* const initialized = await crypto.init()
|
|
1076
|
+
* if (!initialized.success) throw new Error(initialized.error.message)
|
|
1077
|
+
* try {
|
|
1078
|
+
* const generated = crypto.asymmetric.generateKeyPair()
|
|
1079
|
+
* if (!generated.success) throw new Error(generated.error.message)
|
|
1080
|
+
* const pair = generated.data
|
|
1081
|
+
* const publicKey = pair.publicKey
|
|
1082
|
+
* }
|
|
1083
|
+
* finally { await crypto.close() }
|
|
1084
|
+
* ```
|
|
260
1085
|
*/
|
|
261
1086
|
generateKeyPair: () => HaiResult<KeyPair>;
|
|
262
1087
|
/**
|
|
@@ -266,6 +1091,21 @@ interface AsymmetricOperations {
|
|
|
266
1091
|
* @param publicKey - 公钥(十六进制,支持带/不带 04 前缀)
|
|
267
1092
|
* @param options - 加密选项(密文模式、输出格式)
|
|
268
1093
|
* @returns 成功时返回密文字符串;失败时返回 INVALID_KEY 或 ENCRYPTION_FAILED
|
|
1094
|
+
*
|
|
1095
|
+
* @example
|
|
1096
|
+
* ```ts
|
|
1097
|
+
* import { crypto } from '@h-ai/crypto'
|
|
1098
|
+
* const initialized = await crypto.init()
|
|
1099
|
+
* if (!initialized.success) throw new Error(initialized.error.message)
|
|
1100
|
+
* try {
|
|
1101
|
+
* const generated = crypto.asymmetric.generateKeyPair()
|
|
1102
|
+
* if (!generated.success) throw new Error(generated.error.message)
|
|
1103
|
+
* const pair = generated.data
|
|
1104
|
+
* const encrypted = crypto.asymmetric.encrypt('hello', pair.publicKey, { cipherMode: 1, outputFormat: 'hex' })
|
|
1105
|
+
* if (!encrypted.success) throw new Error(encrypted.error.message)
|
|
1106
|
+
* }
|
|
1107
|
+
* finally { await crypto.close() }
|
|
1108
|
+
* ```
|
|
269
1109
|
*/
|
|
270
1110
|
encrypt: (data: string, publicKey: string, options?: AsymmetricEncryptOptions) => HaiResult<string>;
|
|
271
1111
|
/**
|
|
@@ -277,6 +1117,23 @@ interface AsymmetricOperations {
|
|
|
277
1117
|
* @param privateKey - 私钥(64 字符十六进制)
|
|
278
1118
|
* @param options - 解密选项(密文模式需与加密时一致)
|
|
279
1119
|
* @returns 成功时返回明文;失败时返回 INVALID_KEY 或 DECRYPTION_FAILED
|
|
1120
|
+
*
|
|
1121
|
+
* @example
|
|
1122
|
+
* ```ts
|
|
1123
|
+
* import { crypto } from '@h-ai/crypto'
|
|
1124
|
+
* const initialized = await crypto.init()
|
|
1125
|
+
* if (!initialized.success) throw new Error(initialized.error.message)
|
|
1126
|
+
* try {
|
|
1127
|
+
* const generated = crypto.asymmetric.generateKeyPair()
|
|
1128
|
+
* if (!generated.success) throw new Error(generated.error.message)
|
|
1129
|
+
* const pair = generated.data
|
|
1130
|
+
* const encrypted = crypto.asymmetric.encrypt('hello', pair.publicKey)
|
|
1131
|
+
* if (!encrypted.success) throw new Error(encrypted.error.message)
|
|
1132
|
+
* const decrypted = crypto.asymmetric.decrypt(encrypted.data, pair.privateKey)
|
|
1133
|
+
* if (!decrypted.success) throw new Error(decrypted.error.message)
|
|
1134
|
+
* }
|
|
1135
|
+
* finally { await crypto.close() }
|
|
1136
|
+
* ```
|
|
280
1137
|
*/
|
|
281
1138
|
decrypt: (ciphertext: string, privateKey: string, options?: AsymmetricEncryptOptions) => HaiResult<string>;
|
|
282
1139
|
/**
|
|
@@ -286,6 +1143,21 @@ interface AsymmetricOperations {
|
|
|
286
1143
|
* @param privateKey - 私钥(64 字符十六进制)
|
|
287
1144
|
* @param options - 签名选项(hash 开关、userId)
|
|
288
1145
|
* @returns 成功时返回签名字符串;失败时返回 INVALID_KEY 或 SIGN_FAILED
|
|
1146
|
+
*
|
|
1147
|
+
* @example
|
|
1148
|
+
* ```ts
|
|
1149
|
+
* import { crypto } from '@h-ai/crypto'
|
|
1150
|
+
* const initialized = await crypto.init()
|
|
1151
|
+
* if (!initialized.success) throw new Error(initialized.error.message)
|
|
1152
|
+
* try {
|
|
1153
|
+
* const generated = crypto.asymmetric.generateKeyPair()
|
|
1154
|
+
* if (!generated.success) throw new Error(generated.error.message)
|
|
1155
|
+
* const pair = generated.data
|
|
1156
|
+
* const signature = crypto.asymmetric.sign('hello', pair.privateKey)
|
|
1157
|
+
* if (!signature.success) throw new Error(signature.error.message)
|
|
1158
|
+
* }
|
|
1159
|
+
* finally { await crypto.close() }
|
|
1160
|
+
* ```
|
|
289
1161
|
*/
|
|
290
1162
|
sign: (data: string, privateKey: string, options?: SignOptions) => HaiResult<string>;
|
|
291
1163
|
/**
|
|
@@ -296,25 +1168,84 @@ interface AsymmetricOperations {
|
|
|
296
1168
|
* @param publicKey - 公钥(支持带/不带 04 前缀)
|
|
297
1169
|
* @param options - 验签选项
|
|
298
1170
|
* @returns 成功时返回 boolean;失败时返回 INVALID_KEY 或 VERIFY_FAILED
|
|
1171
|
+
*
|
|
1172
|
+
* @example
|
|
1173
|
+
* ```ts
|
|
1174
|
+
* import { crypto } from '@h-ai/crypto'
|
|
1175
|
+
* const initialized = await crypto.init()
|
|
1176
|
+
* if (!initialized.success) throw new Error(initialized.error.message)
|
|
1177
|
+
* try {
|
|
1178
|
+
* const generated = crypto.asymmetric.generateKeyPair()
|
|
1179
|
+
* if (!generated.success) throw new Error(generated.error.message)
|
|
1180
|
+
* const pair = generated.data
|
|
1181
|
+
* const signature = crypto.asymmetric.sign('hello', pair.privateKey)
|
|
1182
|
+
* if (!signature.success) throw new Error(signature.error.message)
|
|
1183
|
+
* const verified = crypto.asymmetric.verify('hello', signature.data, pair.publicKey)
|
|
1184
|
+
* if (!verified.success) throw new Error(verified.error.message)
|
|
1185
|
+
* const accepted = verified.data
|
|
1186
|
+
* }
|
|
1187
|
+
* finally { await crypto.close() }
|
|
1188
|
+
* ```
|
|
299
1189
|
*/
|
|
300
1190
|
verify: (data: string, signature: string, publicKey: string, options?: SignOptions) => HaiResult<boolean>;
|
|
301
1191
|
/**
|
|
302
|
-
*
|
|
1192
|
+
* 校验128/130字符SM2非压缩公钥编码及曲线点;有效不代表对端持有私钥。
|
|
1193
|
+
* @param key - 本次操作的密钥,不记录到日志。
|
|
1194
|
+
* @returns 直接boolean,false表示不匹配/未就绪。
|
|
303
1195
|
*
|
|
304
|
-
*
|
|
1196
|
+
* @example
|
|
1197
|
+
* ```ts
|
|
1198
|
+
* import { crypto } from '@h-ai/crypto'
|
|
1199
|
+
* const initialized = await crypto.init()
|
|
1200
|
+
* if (!initialized.success) throw new Error(initialized.error.message)
|
|
1201
|
+
* try {
|
|
1202
|
+
* const generated = crypto.asymmetric.generateKeyPair()
|
|
1203
|
+
* if (!generated.success) throw new Error(generated.error.message)
|
|
1204
|
+
* const pair = generated.data
|
|
1205
|
+
* const valid = crypto.asymmetric.isValidPublicKey(pair.publicKey)
|
|
1206
|
+
* }
|
|
1207
|
+
* finally { await crypto.close() }
|
|
1208
|
+
* ```
|
|
305
1209
|
*/
|
|
306
1210
|
isValidPublicKey: (key: string) => boolean;
|
|
307
1211
|
/**
|
|
308
|
-
*
|
|
1212
|
+
* 校验64字符十六进制私钥格式;这是格式检查,不是密钥配对证明。
|
|
1213
|
+
* @param key - 本次操作的密钥,不记录到日志。
|
|
1214
|
+
* @returns 直接boolean,false表示不匹配/未就绪。
|
|
309
1215
|
*
|
|
310
|
-
*
|
|
1216
|
+
* @example
|
|
1217
|
+
* ```ts
|
|
1218
|
+
* import { crypto } from '@h-ai/crypto'
|
|
1219
|
+
* const initialized = await crypto.init()
|
|
1220
|
+
* if (!initialized.success) throw new Error(initialized.error.message)
|
|
1221
|
+
* try {
|
|
1222
|
+
* const generated = crypto.asymmetric.generateKeyPair()
|
|
1223
|
+
* if (!generated.success) throw new Error(generated.error.message)
|
|
1224
|
+
* const pair = generated.data
|
|
1225
|
+
* const valid = crypto.asymmetric.isValidPrivateKey(pair.privateKey)
|
|
1226
|
+
* }
|
|
1227
|
+
* finally { await crypto.close() }
|
|
1228
|
+
* ```
|
|
311
1229
|
*/
|
|
312
1230
|
isValidPrivateKey: (key: string) => boolean;
|
|
313
1231
|
}
|
|
314
1232
|
/**
|
|
315
|
-
*
|
|
1233
|
+
* SM3 摘要、HMAC-SM3 与常量时间摘要匹配操作;摘要不是加密,HMAC密钥不能公开。
|
|
316
1234
|
*
|
|
317
|
-
*
|
|
1235
|
+
* @example
|
|
1236
|
+
* ```ts
|
|
1237
|
+
* import { crypto, type HashOperations } from '@h-ai/crypto'
|
|
1238
|
+
* const initialized = await crypto.init()
|
|
1239
|
+
* if (!initialized.success) throw new Error(initialized.error.message)
|
|
1240
|
+
* try {
|
|
1241
|
+
* const operations: HashOperations = crypto.hash
|
|
1242
|
+
* const digest = operations.hash('hello')
|
|
1243
|
+
* if (!digest.success) throw new Error(digest.error.message)
|
|
1244
|
+
* const verified = operations.verify('hello', digest.data)
|
|
1245
|
+
* if (!verified.success) throw new Error(verified.error.message)
|
|
1246
|
+
* }
|
|
1247
|
+
* finally { await crypto.close() }
|
|
1248
|
+
* ```
|
|
318
1249
|
*/
|
|
319
1250
|
interface HashOperations {
|
|
320
1251
|
/**
|
|
@@ -323,6 +1254,18 @@ interface HashOperations {
|
|
|
323
1254
|
* @param data - 待哈希数据(字符串或 Uint8Array)
|
|
324
1255
|
* @param options - 输入编码选项
|
|
325
1256
|
* @returns 成功时返回 64 字符十六进制哈希值;失败时返回 HASH_FAILED
|
|
1257
|
+
*
|
|
1258
|
+
* @example
|
|
1259
|
+
* ```ts
|
|
1260
|
+
* import { crypto } from '@h-ai/crypto'
|
|
1261
|
+
* const initialized = await crypto.init()
|
|
1262
|
+
* if (!initialized.success) throw new Error(initialized.error.message)
|
|
1263
|
+
* try {
|
|
1264
|
+
* const digest = crypto.hash.hash('6869', { inputEncoding: 'hex' })
|
|
1265
|
+
* if (!digest.success) throw new Error(digest.error.message)
|
|
1266
|
+
* }
|
|
1267
|
+
* finally { await crypto.close() }
|
|
1268
|
+
* ```
|
|
326
1269
|
*/
|
|
327
1270
|
hash: (data: string | Uint8Array, options?: HashOptions) => HaiResult<string>;
|
|
328
1271
|
/**
|
|
@@ -334,6 +1277,19 @@ interface HashOperations {
|
|
|
334
1277
|
* @param data - 待计算数据
|
|
335
1278
|
* @param key - HMAC 密钥
|
|
336
1279
|
* @returns 成功时返回 64 字符十六进制 HMAC 值;失败时返回 HMAC_FAILED
|
|
1280
|
+
*
|
|
1281
|
+
* @example
|
|
1282
|
+
* ```ts
|
|
1283
|
+
* import { crypto } from '@h-ai/crypto'
|
|
1284
|
+
* const initialized = await crypto.init()
|
|
1285
|
+
* if (!initialized.success) throw new Error(initialized.error.message)
|
|
1286
|
+
* try {
|
|
1287
|
+
* const key = crypto.symmetric.generateKey()
|
|
1288
|
+
* const tag = crypto.hash.hmac('hello', key)
|
|
1289
|
+
* if (!tag.success) throw new Error(tag.error.message)
|
|
1290
|
+
* }
|
|
1291
|
+
* finally { await crypto.close() }
|
|
1292
|
+
* ```
|
|
337
1293
|
*/
|
|
338
1294
|
hmac: (data: string, key: string) => HaiResult<string>;
|
|
339
1295
|
/**
|
|
@@ -344,19 +1300,77 @@ interface HashOperations {
|
|
|
344
1300
|
* @param data - 原始数据
|
|
345
1301
|
* @param expectedHash - 期望的哈希值
|
|
346
1302
|
* @returns 成功时返回 boolean;失败时返回 HASH_FAILED
|
|
1303
|
+
*
|
|
1304
|
+
* @example
|
|
1305
|
+
* ```ts
|
|
1306
|
+
* import { crypto } from '@h-ai/crypto'
|
|
1307
|
+
* const initialized = await crypto.init()
|
|
1308
|
+
* if (!initialized.success) throw new Error(initialized.error.message)
|
|
1309
|
+
* try {
|
|
1310
|
+
* const digest = crypto.hash.hash('hello')
|
|
1311
|
+
* if (!digest.success) throw new Error(digest.error.message)
|
|
1312
|
+
* const verified = crypto.hash.verify('hello', digest.data)
|
|
1313
|
+
* if (!verified.success) throw new Error(verified.error.message)
|
|
1314
|
+
* const matches = verified.data
|
|
1315
|
+
* }
|
|
1316
|
+
* finally { await crypto.close() }
|
|
1317
|
+
* ```
|
|
347
1318
|
*/
|
|
348
1319
|
verify: (data: string, expectedHash: string) => HaiResult<boolean>;
|
|
349
1320
|
}
|
|
350
1321
|
/**
|
|
351
|
-
*
|
|
1322
|
+
* SM4 同步操作域;默认CBC与随机IV,返回完整结构化密文;CBC原语不提供完整性认证。
|
|
352
1323
|
*
|
|
353
|
-
*
|
|
354
|
-
*
|
|
1324
|
+
* @example
|
|
1325
|
+
* ```ts
|
|
1326
|
+
* import { crypto, type SymmetricOperations } from '@h-ai/crypto'
|
|
1327
|
+
* const initialized = await crypto.init()
|
|
1328
|
+
* if (!initialized.success) throw new Error(initialized.error.message)
|
|
1329
|
+
* try {
|
|
1330
|
+
* const operations: SymmetricOperations = crypto.symmetric
|
|
1331
|
+
* const key = operations.generateKey()
|
|
1332
|
+
* const encrypted = operations.encrypt('hello', key)
|
|
1333
|
+
* if (!encrypted.success) throw new Error(encrypted.error.message)
|
|
1334
|
+
* const decrypted = operations.decrypt(encrypted.data, key)
|
|
1335
|
+
* if (!decrypted.success) throw new Error(decrypted.error.message)
|
|
1336
|
+
* }
|
|
1337
|
+
* finally { await crypto.close() }
|
|
1338
|
+
* ```
|
|
355
1339
|
*/
|
|
356
1340
|
interface SymmetricOperations {
|
|
357
|
-
/**
|
|
1341
|
+
/**
|
|
1342
|
+
* 生成随机密钥(16 字节 = 32 个十六进制字符)
|
|
1343
|
+
* @returns 直接返回值,使用前先初始化模块。
|
|
1344
|
+
*
|
|
1345
|
+
* @example
|
|
1346
|
+
* ```ts
|
|
1347
|
+
* import { crypto } from '@h-ai/crypto'
|
|
1348
|
+
* const initialized = await crypto.init()
|
|
1349
|
+
* if (!initialized.success) throw new Error(initialized.error.message)
|
|
1350
|
+
* try {
|
|
1351
|
+
* const key = crypto.symmetric.generateKey()
|
|
1352
|
+
* const valid = crypto.symmetric.isValidKey(key)
|
|
1353
|
+
* }
|
|
1354
|
+
* finally { await crypto.close() }
|
|
1355
|
+
* ```
|
|
1356
|
+
*/
|
|
358
1357
|
generateKey: () => string;
|
|
359
|
-
/**
|
|
1358
|
+
/**
|
|
1359
|
+
* 生成随机 IV(16 字节 = 32 个十六进制字符)
|
|
1360
|
+
* @returns 直接返回值,使用前先初始化模块。
|
|
1361
|
+
*
|
|
1362
|
+
* @example
|
|
1363
|
+
* ```ts
|
|
1364
|
+
* import { crypto } from '@h-ai/crypto'
|
|
1365
|
+
* const initialized = await crypto.init()
|
|
1366
|
+
* if (!initialized.success) throw new Error(initialized.error.message)
|
|
1367
|
+
* try {
|
|
1368
|
+
* const iv = crypto.symmetric.generateIV()
|
|
1369
|
+
* const valid = crypto.symmetric.isValidIV(iv)
|
|
1370
|
+
* }
|
|
1371
|
+
* finally { await crypto.close() }
|
|
1372
|
+
* ```
|
|
1373
|
+
*/
|
|
360
1374
|
generateIV: () => string;
|
|
361
1375
|
/**
|
|
362
1376
|
* 对称加密
|
|
@@ -368,6 +1382,19 @@ interface SymmetricOperations {
|
|
|
368
1382
|
* @param key - 密钥(32 字符十六进制)
|
|
369
1383
|
* @param options - 加密模式/IV/输出格式
|
|
370
1384
|
* @returns 成功时返回结构化密文;失败时返回 INVALID_KEY/INVALID_IV/ENCRYPTION_FAILED
|
|
1385
|
+
*
|
|
1386
|
+
* @example
|
|
1387
|
+
* ```ts
|
|
1388
|
+
* import { crypto } from '@h-ai/crypto'
|
|
1389
|
+
* const initialized = await crypto.init()
|
|
1390
|
+
* if (!initialized.success) throw new Error(initialized.error.message)
|
|
1391
|
+
* try {
|
|
1392
|
+
* const key = crypto.symmetric.generateKey()
|
|
1393
|
+
* const encrypted = crypto.symmetric.encrypt('hello', key)
|
|
1394
|
+
* if (!encrypted.success) throw new Error(encrypted.error.message)
|
|
1395
|
+
* }
|
|
1396
|
+
* finally { await crypto.close() }
|
|
1397
|
+
* ```
|
|
371
1398
|
*/
|
|
372
1399
|
encrypt: (data: string, key: string, options?: SymmetricEncryptOptions) => HaiResult<SymmetricEncryptedPayload>;
|
|
373
1400
|
/**
|
|
@@ -378,6 +1405,21 @@ interface SymmetricOperations {
|
|
|
378
1405
|
* @param payload - 结构化密文(包含 mode/ciphertext/iv/encoding)
|
|
379
1406
|
* @param key - 密钥(32 字符十六进制)
|
|
380
1407
|
* @returns 成功时返回明文;失败时返回 INVALID_KEY/INVALID_IV/DECRYPTION_FAILED
|
|
1408
|
+
*
|
|
1409
|
+
* @example
|
|
1410
|
+
* ```ts
|
|
1411
|
+
* import { crypto } from '@h-ai/crypto'
|
|
1412
|
+
* const initialized = await crypto.init()
|
|
1413
|
+
* if (!initialized.success) throw new Error(initialized.error.message)
|
|
1414
|
+
* try {
|
|
1415
|
+
* const key = crypto.symmetric.generateKey()
|
|
1416
|
+
* const encrypted = crypto.symmetric.encrypt('hello', key)
|
|
1417
|
+
* if (!encrypted.success) throw new Error(encrypted.error.message)
|
|
1418
|
+
* const decrypted = crypto.symmetric.decrypt(encrypted.data, key)
|
|
1419
|
+
* if (!decrypted.success) throw new Error(decrypted.error.message)
|
|
1420
|
+
* }
|
|
1421
|
+
* finally { await crypto.close() }
|
|
1422
|
+
* ```
|
|
381
1423
|
*/
|
|
382
1424
|
decrypt: (payload: SymmetricDecryptInput, key: string) => HaiResult<string>;
|
|
383
1425
|
/**
|
|
@@ -385,43 +1427,103 @@ interface SymmetricOperations {
|
|
|
385
1427
|
*
|
|
386
1428
|
* @param data - 待加密明文
|
|
387
1429
|
* @param key - 密钥(32 字符十六进制)
|
|
388
|
-
* @returns
|
|
1430
|
+
* @returns 成功时返回完整 { mode, ciphertext, iv, encoding };失败时同 encrypt
|
|
1431
|
+
*
|
|
1432
|
+
* @example
|
|
1433
|
+
* ```ts
|
|
1434
|
+
* import { crypto } from '@h-ai/crypto'
|
|
1435
|
+
* const initialized = await crypto.init()
|
|
1436
|
+
* if (!initialized.success) throw new Error(initialized.error.message)
|
|
1437
|
+
* try {
|
|
1438
|
+
* const key = crypto.symmetric.generateKey()
|
|
1439
|
+
* const encrypted = crypto.symmetric.encryptWithIV('hello', key)
|
|
1440
|
+
* if (!encrypted.success) throw new Error(encrypted.error.message)
|
|
1441
|
+
* }
|
|
1442
|
+
* finally { await crypto.close() }
|
|
1443
|
+
* ```
|
|
389
1444
|
*/
|
|
390
1445
|
encryptWithIV: (data: string, key: string) => HaiResult<EncryptWithIVResult>;
|
|
391
1446
|
/**
|
|
392
1447
|
* 带 IV 解密(CBC 模式)
|
|
393
1448
|
*
|
|
1449
|
+
* 不提供完整性认证。传输场景必须用 transport 先验签和验证 MAC,禁止直接对 HTTP 密文调用此 primitive。
|
|
1450
|
+
*
|
|
394
1451
|
* @param ciphertext - 密文
|
|
395
1452
|
* @param key - 密钥
|
|
396
1453
|
* @param iv - 加密时使用的 IV
|
|
397
1454
|
* @returns 成功时返回明文;失败时同 decrypt
|
|
1455
|
+
*
|
|
1456
|
+
* @example
|
|
1457
|
+
* ```ts
|
|
1458
|
+
* import { crypto } from '@h-ai/crypto'
|
|
1459
|
+
* const initialized = await crypto.init()
|
|
1460
|
+
* if (!initialized.success) throw new Error(initialized.error.message)
|
|
1461
|
+
* try {
|
|
1462
|
+
* const key = crypto.symmetric.generateKey()
|
|
1463
|
+
* const encrypted = crypto.symmetric.encryptWithIV('hello', key)
|
|
1464
|
+
* if (!encrypted.success) throw new Error(encrypted.error.message)
|
|
1465
|
+
* const decrypted = crypto.symmetric.decryptWithIV(encrypted.data.ciphertext, key, encrypted.data.iv)
|
|
1466
|
+
* if (!decrypted.success) throw new Error(decrypted.error.message)
|
|
1467
|
+
* }
|
|
1468
|
+
* finally { await crypto.close() }
|
|
1469
|
+
* ```
|
|
398
1470
|
*/
|
|
399
1471
|
decryptWithIV: (ciphertext: string, key: string, iv: string) => HaiResult<string>;
|
|
400
1472
|
/**
|
|
401
|
-
*
|
|
402
|
-
*
|
|
403
|
-
*
|
|
404
|
-
*
|
|
405
|
-
* ⚠️ 安全警告:**此实现不是标准 KDF**,不具备密码爆破抗性。
|
|
406
|
-
* - 禁止用于密码存储(请用 `crypto.password.hash`)。
|
|
407
|
-
* - 禁止用于高价值密钥派生(请在应用层采用 PBKDF2 / scrypt / Argon2)。
|
|
1473
|
+
* 校验密钥格式是否合法(32 字符十六进制)
|
|
1474
|
+
* @param key - 本次操作的密钥,不记录到日志。
|
|
1475
|
+
* @returns 直接boolean,false表示不匹配/未就绪。
|
|
408
1476
|
*
|
|
409
|
-
* @
|
|
410
|
-
*
|
|
411
|
-
*
|
|
412
|
-
*
|
|
1477
|
+
* @example
|
|
1478
|
+
* ```ts
|
|
1479
|
+
* import { crypto } from '@h-ai/crypto'
|
|
1480
|
+
* const initialized = await crypto.init()
|
|
1481
|
+
* if (!initialized.success) throw new Error(initialized.error.message)
|
|
1482
|
+
* try {
|
|
1483
|
+
* const key = crypto.symmetric.generateKey()
|
|
1484
|
+
* const valid = crypto.symmetric.isValidKey(key)
|
|
1485
|
+
* }
|
|
1486
|
+
* finally { await crypto.close() }
|
|
1487
|
+
* ```
|
|
413
1488
|
*/
|
|
414
|
-
deriveKey: (password: string, salt: string) => string;
|
|
415
|
-
/** 校验密钥格式是否合法(32 字符十六进制) */
|
|
416
1489
|
isValidKey: (key: string) => boolean;
|
|
417
|
-
/**
|
|
1490
|
+
/**
|
|
1491
|
+
* 校验 IV 格式是否合法(32 字符十六进制)
|
|
1492
|
+
* @param iv - 实际调用输入,按该方法约束验证。
|
|
1493
|
+
* @returns 直接boolean,false表示不匹配/未就绪。
|
|
1494
|
+
*
|
|
1495
|
+
* @example
|
|
1496
|
+
* ```ts
|
|
1497
|
+
* import { crypto } from '@h-ai/crypto'
|
|
1498
|
+
* const initialized = await crypto.init()
|
|
1499
|
+
* if (!initialized.success) throw new Error(initialized.error.message)
|
|
1500
|
+
* try {
|
|
1501
|
+
* const iv = crypto.symmetric.generateIV()
|
|
1502
|
+
* const valid = crypto.symmetric.isValidIV(iv)
|
|
1503
|
+
* }
|
|
1504
|
+
* finally { await crypto.close() }
|
|
1505
|
+
* ```
|
|
1506
|
+
*/
|
|
418
1507
|
isValidIV: (iv: string) => boolean;
|
|
419
1508
|
}
|
|
420
1509
|
/**
|
|
421
|
-
*
|
|
1510
|
+
* 迭代加盐SM3密码存储操作;验证从 $hai$ 格式读取参数,返回成功false表示密码不匹配。
|
|
422
1511
|
*
|
|
423
|
-
*
|
|
424
|
-
*
|
|
1512
|
+
* @example
|
|
1513
|
+
* ```ts
|
|
1514
|
+
* import { crypto, type PasswordOperations } from '@h-ai/crypto'
|
|
1515
|
+
* const initialized = await crypto.init()
|
|
1516
|
+
* if (!initialized.success) throw new Error(initialized.error.message)
|
|
1517
|
+
* try {
|
|
1518
|
+
* const operations: PasswordOperations = crypto.password
|
|
1519
|
+
* const hashed = operations.hash('test-password')
|
|
1520
|
+
* if (!hashed.success) throw new Error(hashed.error.message)
|
|
1521
|
+
* const verified = operations.verify('test-password', hashed.data)
|
|
1522
|
+
* if (!verified.success) throw new Error(verified.error.message)
|
|
1523
|
+
* const matches = verified.data
|
|
1524
|
+
* }
|
|
1525
|
+
* finally { await crypto.close() }
|
|
1526
|
+
* ```
|
|
425
1527
|
*/
|
|
426
1528
|
interface PasswordOperations {
|
|
427
1529
|
/**
|
|
@@ -432,6 +1534,18 @@ interface PasswordOperations {
|
|
|
432
1534
|
* @param password - 明文密码(不能为空)
|
|
433
1535
|
* @param config - 可选配置(盐值长度、迭代次数)
|
|
434
1536
|
* @returns 成功时返回格式化的哈希字符串;失败时返回 INVALID_INPUT 或 HASH_FAILED
|
|
1537
|
+
*
|
|
1538
|
+
* @example
|
|
1539
|
+
* ```ts
|
|
1540
|
+
* import { crypto } from '@h-ai/crypto'
|
|
1541
|
+
* const initialized = await crypto.init()
|
|
1542
|
+
* if (!initialized.success) throw new Error(initialized.error.message)
|
|
1543
|
+
* try {
|
|
1544
|
+
* const hashed = crypto.password.hash('test-password', { iterations: 10000, saltLength: 16 })
|
|
1545
|
+
* if (!hashed.success) throw new Error(hashed.error.message)
|
|
1546
|
+
* }
|
|
1547
|
+
* finally { await crypto.close() }
|
|
1548
|
+
* ```
|
|
435
1549
|
*/
|
|
436
1550
|
hash: (password: string, config?: PasswordConfig) => HaiResult<string>;
|
|
437
1551
|
/**
|
|
@@ -442,61 +1556,86 @@ interface PasswordOperations {
|
|
|
442
1556
|
* @param password - 待验证的明文密码
|
|
443
1557
|
* @param hash - 存储的哈希值(格式: `$hai$<iterations>$<salt>$<hash>`)
|
|
444
1558
|
* @returns 成功时返回 boolean;失败时返回 INVALID_INPUT 或 VERIFY_FAILED
|
|
1559
|
+
*
|
|
1560
|
+
* @example
|
|
1561
|
+
* ```ts
|
|
1562
|
+
* import { crypto } from '@h-ai/crypto'
|
|
1563
|
+
* const initialized = await crypto.init()
|
|
1564
|
+
* if (!initialized.success) throw new Error(initialized.error.message)
|
|
1565
|
+
* try {
|
|
1566
|
+
* const hashed = crypto.password.hash('test-password')
|
|
1567
|
+
* if (!hashed.success) throw new Error(hashed.error.message)
|
|
1568
|
+
* const verified = crypto.password.verify('test-password', hashed.data)
|
|
1569
|
+
* if (!verified.success) throw new Error(verified.error.message)
|
|
1570
|
+
* const matches = verified.data
|
|
1571
|
+
* }
|
|
1572
|
+
* finally { await crypto.close() }
|
|
1573
|
+
* ```
|
|
445
1574
|
*/
|
|
446
1575
|
verify: (password: string, hash: string) => HaiResult<boolean>;
|
|
447
1576
|
}
|
|
448
1577
|
/**
|
|
449
|
-
*
|
|
450
|
-
*
|
|
451
|
-
* 通过 `crypto.transport` 访问,需先调用 `crypto.init()`。
|
|
452
|
-
*
|
|
453
|
-
* @remarks
|
|
454
|
-
* 使用流程(直接使用底层 transport 工厂时):
|
|
455
|
-
*
|
|
456
|
-
* 1. 先 `await crypto.init()`,确保 `crypto.asymmetric` / `crypto.symmetric` 已就绪。
|
|
457
|
-
* 2. 服务端调用 `crypto.transport.createServer()` 创建 `TransportEncryptionManager`;该管理器负责持有服务端密钥对,并管理客户端公钥。多节点部署时可通过 `options.keyStore` 注入共享存储。
|
|
458
|
-
* 3. 服务端暴露一个 POST 密钥协商端点:接收 `{ clientPublicKey }`,调用 `manager.registerClientKey()` 注册客户端公钥,再返回 `{ serverPublicKey: manager.getServerPublicKey(), clientId }`。
|
|
459
|
-
* 4. 客户端调用 `crypto.transport.createClient({ keyExchangeUrl })` 创建会话;首次 `client.init()` 或 `client.encryptedFetch()` 会自动完成密钥协商。
|
|
460
|
-
* 5. 协商完成后,客户端每次请求都会附带 `X-Client-Id`;若请求有 body,则 body 会被包装成 `{ encryptedKey, ciphertext, iv }`,并设置 `X-Encrypted: true`。
|
|
461
|
-
* 6. 服务端在业务逻辑前调用 `manager.decryptRequest()` 解密请求体,在返回前调用 `manager.encryptResponse(clientId, data)` 为当前客户端重新加密响应。
|
|
462
|
-
* 7. 客户端收到 `X-Encrypted: true` 的响应后会自动解密;调用 `client.destroy()` 可清空当前会话,下次请求会重新协商。
|
|
1578
|
+
* 认证传输工厂;需先初始化crypto,服务端显式受管密钥环+共享registry,客户端通过TLS协商公钥。
|
|
1579
|
+
* 协议v2绑定方向、HTTP方法/路径/状态、密钥版本和nonce,验签/MAC成功后才解密;防重放不替代业务幂等。
|
|
463
1580
|
*
|
|
464
|
-
*
|
|
465
|
-
* - `serv.createApp({ transport: { crypto } })`
|
|
466
|
-
* - `kit.createHandle({ crypto: { crypto, transport: true } })`
|
|
467
|
-
* - `apiClient.init({ transport: { crypto } })`
|
|
468
|
-
*
|
|
469
|
-
* @example 服务端
|
|
470
|
-
* ```ts
|
|
471
|
-
* const result = crypto.transport.createServer({
|
|
472
|
-
* keyStore: createRedisTransportKeyStore({ cache, ttlSeconds: 3600 }),
|
|
473
|
-
* })
|
|
474
|
-
* if (!result.success) throw result.error
|
|
475
|
-
* const manager = result.data
|
|
476
|
-
* ```
|
|
477
|
-
*
|
|
478
|
-
* @example 客户端
|
|
1581
|
+
* @example
|
|
479
1582
|
* ```ts
|
|
480
|
-
*
|
|
481
|
-
*
|
|
482
|
-
*
|
|
483
|
-
*
|
|
484
|
-
*
|
|
485
|
-
*
|
|
1583
|
+
* import { crypto } from '@h-ai/crypto'
|
|
1584
|
+
* const initialized = await crypto.init()
|
|
1585
|
+
* if (!initialized.success) throw new Error(initialized.error.message)
|
|
1586
|
+
* try {
|
|
1587
|
+
* const client = crypto.transport.createClient({ keyExchangeUrl: 'https://api.example.com/api/v1/_hai/key-exchange' })
|
|
1588
|
+
* try {
|
|
1589
|
+
* const ready = await client.init()
|
|
1590
|
+
* if (!ready.success) throw new Error(ready.error.message)
|
|
1591
|
+
* const response = await client.encryptedFetch('https://api.example.com/api/v1/orders')
|
|
1592
|
+
* if (!response.ok) throw new Error('HTTP request failed')
|
|
1593
|
+
* }
|
|
1594
|
+
* finally { client.destroy() }
|
|
1595
|
+
* }
|
|
1596
|
+
* finally { await crypto.close() }
|
|
486
1597
|
* ```
|
|
487
1598
|
*/
|
|
488
1599
|
interface TransportOperations {
|
|
489
1600
|
/**
|
|
490
1601
|
* 创建服务端传输加密管理器。
|
|
491
1602
|
*
|
|
492
|
-
*
|
|
493
|
-
*
|
|
494
|
-
* 成功返回 manager;密钥生成失败返回 `HaiCommonError.INTERNAL_ERROR`。
|
|
1603
|
+
* serverKeyRing/keyStore 均必填;生产使用相同受管密钥环与共享 registry。
|
|
1604
|
+
* 成功返回 manager;缺失、格式错误或公私钥错配返回脱敏 HaiResult 错误。
|
|
495
1605
|
*
|
|
496
1606
|
* @remarks
|
|
497
1607
|
* 通常在服务启动阶段创建一次,并交给 HTTP 中间件 / 路由处理器复用。
|
|
1608
|
+
* @param options - 实际调用输入,按该方法约束验证。
|
|
1609
|
+
* @returns HaiResult,先检查success再读取data。
|
|
1610
|
+
*
|
|
1611
|
+
* @example
|
|
1612
|
+
* ```ts
|
|
1613
|
+
* import { crypto, createRedisTransportKeyStore, parseTransportServerKeyRing } from '@h-ai/crypto'
|
|
1614
|
+
* import { cache } from '@h-ai/cache'
|
|
1615
|
+
* const initialized = await crypto.init()
|
|
1616
|
+
* if (!initialized.success) throw new Error(initialized.error.message)
|
|
1617
|
+
* try {
|
|
1618
|
+
* const connected = await cache.init({ type: 'redis', host: 'cache', port: 6379, password: process.env.HAI_CACHE_PASSWORD })
|
|
1619
|
+
* if (!connected.success) throw new Error(connected.error.message)
|
|
1620
|
+
* try {
|
|
1621
|
+
* const registry = createRedisTransportKeyStore({ cache, ttlSeconds: 3600 })
|
|
1622
|
+
* const ring = parseTransportServerKeyRing(process.env.HAI_CRYPTO_TRANSPORT_SERVERKEYRING)
|
|
1623
|
+
* if (!ring.success) throw new Error(ring.error.message)
|
|
1624
|
+
* const created = crypto.transport.createServer({ serverKeyRing: ring.data, keyStore: registry })
|
|
1625
|
+
* if (!created.success) throw new Error(created.error.message)
|
|
1626
|
+
* const manager = created.data
|
|
1627
|
+
* try {
|
|
1628
|
+
* const currentVersion = manager.getServerKeyId()
|
|
1629
|
+
* const publicKey = manager.getServerPublicKey()
|
|
1630
|
+
* }
|
|
1631
|
+
* finally { await manager.close() }
|
|
1632
|
+
* }
|
|
1633
|
+
* finally { await cache.close() }
|
|
1634
|
+
* }
|
|
1635
|
+
* finally { await crypto.close() }
|
|
1636
|
+
* ```
|
|
498
1637
|
*/
|
|
499
|
-
createServer: (options
|
|
1638
|
+
createServer: (options: TransportCreateServerOptions) => HaiResult<TransportEncryptionManager>;
|
|
500
1639
|
/**
|
|
501
1640
|
* 创建客户端传输加密会话。
|
|
502
1641
|
*
|
|
@@ -506,6 +1645,28 @@ interface TransportOperations {
|
|
|
506
1645
|
* @remarks
|
|
507
1646
|
* 返回的是“单会话”客户端:同一个实例会复用已协商得到的 `clientId` 与服务端公钥;
|
|
508
1647
|
* 调用 `destroy()` 后会回到未协商状态。
|
|
1648
|
+
* @param options - 实际调用输入,按该方法约束验证。
|
|
1649
|
+
* @param options.keyExchangeUrl - 密钥协商端点完整 URL。
|
|
1650
|
+
* @param options.fetch - 实际发送 HTTP 请求的 fetch,默认 `globalThis.fetch`。
|
|
1651
|
+
* @returns 直接返回值,使用前先初始化模块。
|
|
1652
|
+
*
|
|
1653
|
+
* @example
|
|
1654
|
+
* ```ts
|
|
1655
|
+
* import { crypto } from '@h-ai/crypto'
|
|
1656
|
+
* const initialized = await crypto.init()
|
|
1657
|
+
* if (!initialized.success) throw new Error(initialized.error.message)
|
|
1658
|
+
* try {
|
|
1659
|
+
* const client = crypto.transport.createClient({ keyExchangeUrl: 'https://api.example.com/api/v1/_hai/key-exchange' })
|
|
1660
|
+
* try {
|
|
1661
|
+
* const ready = await client.init()
|
|
1662
|
+
* if (!ready.success) throw new Error(ready.error.message)
|
|
1663
|
+
* const response = await client.encryptedFetch('https://api.example.com/api/v1/orders')
|
|
1664
|
+
* if (!response.ok) throw new Error('HTTP request failed')
|
|
1665
|
+
* }
|
|
1666
|
+
* finally { client.destroy() }
|
|
1667
|
+
* }
|
|
1668
|
+
* finally { await crypto.close() }
|
|
1669
|
+
* ```
|
|
509
1670
|
*/
|
|
510
1671
|
createClient: (options: {
|
|
511
1672
|
keyExchangeUrl: string;
|
|
@@ -515,17 +1676,19 @@ interface TransportOperations {
|
|
|
515
1676
|
readonly protocol: typeof TRANSPORT_PROTOCOL;
|
|
516
1677
|
}
|
|
517
1678
|
/**
|
|
518
|
-
*
|
|
519
|
-
*
|
|
520
|
-
* `crypto` 服务对象的类型定义。使用前必须调用 `init()` 初始化。
|
|
1679
|
+
* 加密模块服务接口;init检查HaiResult,close返回void,调用不能把原语当作已认证HTTP协议。
|
|
521
1680
|
*
|
|
522
1681
|
* @example
|
|
523
1682
|
* ```ts
|
|
524
1683
|
* import { crypto } from '@h-ai/crypto'
|
|
525
|
-
*
|
|
526
|
-
*
|
|
527
|
-
*
|
|
528
|
-
*
|
|
1684
|
+
* const initialized = await crypto.init()
|
|
1685
|
+
* if (!initialized.success) throw new Error(initialized.error.message)
|
|
1686
|
+
* try {
|
|
1687
|
+
* const digest = crypto.hash.hash('hello')
|
|
1688
|
+
* if (!digest.success) throw new Error(digest.error.message)
|
|
1689
|
+
* const value = digest.data
|
|
1690
|
+
* }
|
|
1691
|
+
* finally { await crypto.close() }
|
|
529
1692
|
* ```
|
|
530
1693
|
*/
|
|
531
1694
|
interface CryptoFunctions {
|
|
@@ -536,12 +1699,30 @@ interface CryptoFunctions {
|
|
|
536
1699
|
* 重复调用会先关闭再重新初始化。
|
|
537
1700
|
*
|
|
538
1701
|
* @returns 成功时返回 ok(undefined);失败时返回 INIT_FAILED
|
|
1702
|
+
*
|
|
1703
|
+
* @example
|
|
1704
|
+
* ```ts
|
|
1705
|
+
* import { crypto } from '@h-ai/crypto'
|
|
1706
|
+
* const initialized = await crypto.init()
|
|
1707
|
+
* if (!initialized.success) throw new Error(initialized.error.message)
|
|
1708
|
+
* await crypto.close()
|
|
1709
|
+
* ```
|
|
539
1710
|
*/
|
|
540
1711
|
init: () => Promise<HaiResult<void>>;
|
|
541
1712
|
/**
|
|
542
1713
|
* 关闭加密模块,释放内部状态
|
|
543
1714
|
*
|
|
544
1715
|
* 关闭后再访问 asymmetric/hash/symmetric/password 会返回 NOT_INITIALIZED 错误。
|
|
1716
|
+
* @returns 异步操作结果;存储或网络故障按方法契约拒绝。
|
|
1717
|
+
*
|
|
1718
|
+
* @example
|
|
1719
|
+
* ```ts
|
|
1720
|
+
* import { crypto } from '@h-ai/crypto'
|
|
1721
|
+
* const initialized = await crypto.init()
|
|
1722
|
+
* if (!initialized.success) throw new Error(initialized.error.message)
|
|
1723
|
+
* await crypto.close()
|
|
1724
|
+
* const ready = crypto.isInitialized
|
|
1725
|
+
* ```
|
|
545
1726
|
*/
|
|
546
1727
|
close: () => Promise<void>;
|
|
547
1728
|
/** 是否已初始化 */
|
|
@@ -566,38 +1747,196 @@ interface CryptoFunctions {
|
|
|
566
1747
|
*/
|
|
567
1748
|
|
|
568
1749
|
/**
|
|
569
|
-
*
|
|
570
|
-
*
|
|
571
|
-
* 使用前必须调用 `crypto.init()` 进行初始化。
|
|
572
|
-
* 未初始化时访问 asymmetric/hash/symmetric/password 的任何方法均返回 NOT_INITIALIZED 错误。
|
|
1750
|
+
* 加密模块公开服务对象;先检查 init 的 HaiResult,再使用各操作域,最后 close。
|
|
1751
|
+
* SM2/SM3/SM4 原语不等于认证传输;HTTP 使用 transport 的签名、MAC、上下文和共享 nonce 校验。
|
|
573
1752
|
*
|
|
574
1753
|
* @example
|
|
575
1754
|
* ```ts
|
|
576
1755
|
* import { crypto } from '@h-ai/crypto'
|
|
577
|
-
*
|
|
578
|
-
*
|
|
579
|
-
*
|
|
580
|
-
*
|
|
581
|
-
*
|
|
1756
|
+
* const initialized = await crypto.init()
|
|
1757
|
+
* if (!initialized.success) throw new Error(initialized.error.message)
|
|
1758
|
+
* try {
|
|
1759
|
+
* const generated = crypto.asymmetric.generateKeyPair()
|
|
1760
|
+
* if (!generated.success) throw new Error(generated.error.message)
|
|
1761
|
+
* const pair = generated.data
|
|
1762
|
+
* const digest = crypto.hash.hash('hello')
|
|
1763
|
+
* if (!digest.success) throw new Error(digest.error.message)
|
|
1764
|
+
* const publicKeyValid = crypto.asymmetric.isValidPublicKey(pair.publicKey)
|
|
1765
|
+
* }
|
|
1766
|
+
* finally { await crypto.close() }
|
|
582
1767
|
* ```
|
|
583
1768
|
*/
|
|
584
1769
|
declare const crypto: CryptoFunctions;
|
|
585
1770
|
|
|
1771
|
+
/**
|
|
1772
|
+
* @h-ai/crypto — SM2 公钥输入校验。
|
|
1773
|
+
* @module crypto-public-key
|
|
1774
|
+
*/
|
|
1775
|
+
/**
|
|
1776
|
+
* 纯函数校验 SM2 公钥的编码及曲线点,不初始化模块或访问 registry。
|
|
1777
|
+
* 仅接受128字符无前缀或130字符04前缀的十六进制非压缩点;拒绝曲线外点及其它编码。
|
|
1778
|
+
* @param publicKey - 不可信的输入。
|
|
1779
|
+
* @returns 有效字符串时为 true,并收窄类型;不证明密钥所有权,注册后仍须验签。
|
|
1780
|
+
*
|
|
1781
|
+
* @example
|
|
1782
|
+
* ```ts
|
|
1783
|
+
* import { crypto, isValidSM2PublicKey } from '@h-ai/crypto'
|
|
1784
|
+
* const initialized = await crypto.init()
|
|
1785
|
+
* if (!initialized.success) throw new Error(initialized.error.message)
|
|
1786
|
+
* try {
|
|
1787
|
+
* const generated = crypto.asymmetric.generateKeyPair()
|
|
1788
|
+
* if (!generated.success) throw new Error(generated.error.message)
|
|
1789
|
+
* const pair = generated.data
|
|
1790
|
+
* const valid = isValidSM2PublicKey(pair.publicKey)
|
|
1791
|
+
* const invalid = isValidSM2PublicKey('not-a-public-key')
|
|
1792
|
+
* }
|
|
1793
|
+
* finally { await crypto.close() }
|
|
1794
|
+
* ```
|
|
1795
|
+
*/
|
|
1796
|
+
declare function isValidSM2PublicKey(publicKey: unknown): publicKey is string;
|
|
1797
|
+
|
|
1798
|
+
/**
|
|
1799
|
+
* @h-ai/crypto — 传输协议的字节预算与有界正文读取。
|
|
1800
|
+
*
|
|
1801
|
+
* serv/kit 共享相同预算,不依赖可省略或伪造的 Content-Length。
|
|
1802
|
+
* @module crypto-transport-body
|
|
1803
|
+
*/
|
|
1804
|
+
/**
|
|
1805
|
+
* 协议固定UTF-8字节预算;按实际流读取,不依赖Content-Length,非字符串字符数限制。
|
|
1806
|
+
*
|
|
1807
|
+
* @example
|
|
1808
|
+
* ```ts
|
|
1809
|
+
* import { readTransportBody, TRANSPORT_BODY_LIMITS } from '@h-ai/crypto'
|
|
1810
|
+
* const request = new Request('https://api.example.com/api/v1/orders', { method: 'POST', body: JSON.stringify({ message: 'hello' }) })
|
|
1811
|
+
* const text = await readTransportBody(request.body, { maxBytes: TRANSPORT_BODY_LIMITS.plaintextBytes, signal: request.signal })
|
|
1812
|
+
* ```
|
|
1813
|
+
*/
|
|
1814
|
+
declare const TRANSPORT_BODY_LIMITS: {
|
|
1815
|
+
/** 密钥协商 JSON 的 UTF-8 总字节数。 */
|
|
1816
|
+
readonly keyExchangeBytes: 16384;
|
|
1817
|
+
/** GET/HEAD 请求及无正文响应的认证头封装 UTF-8 上限。 */
|
|
1818
|
+
readonly envelopeHeaderBytes: 16384;
|
|
1819
|
+
/** 单个协商公钥的 UTF-8 字节数。 */
|
|
1820
|
+
readonly publicKeyBytes: 4096;
|
|
1821
|
+
/** 解密后的请求/加密前的响应明文字节数。 */
|
|
1822
|
+
readonly plaintextBytes: 1048576;
|
|
1823
|
+
/** 密文 JSON 的 UTF-8 总字节数(2 MiB + 32 KiB,含 CBC 填充与全部认证字段)。 */
|
|
1824
|
+
readonly encryptedPayloadBytes: 2129920;
|
|
1825
|
+
};
|
|
1826
|
+
/**
|
|
1827
|
+
* 有界原流读取选项;maxBytes是非负安全整数,signal取消等待和底层流。
|
|
1828
|
+
*
|
|
1829
|
+
* @example
|
|
1830
|
+
* ```ts
|
|
1831
|
+
* import { readTransportBody, type ReadTransportBodyOptions } from '@h-ai/crypto'
|
|
1832
|
+
* const request = new Request('https://api.example.com/api/v1/orders', { method: 'POST', body: 'hello' })
|
|
1833
|
+
* const options: ReadTransportBodyOptions = { maxBytes: 1024, signal: request.signal }
|
|
1834
|
+
* const text = await readTransportBody(request.body, options)
|
|
1835
|
+
* ```
|
|
1836
|
+
*/
|
|
1837
|
+
interface ReadTransportBodyOptions {
|
|
1838
|
+
/** 允许读取的字节总数,必须为非负安全整数。 */
|
|
1839
|
+
readonly maxBytes: number;
|
|
1840
|
+
/** 请求取消时中止等待中的读取并取消底层流。 */
|
|
1841
|
+
readonly signal?: AbortSignal;
|
|
1842
|
+
}
|
|
1843
|
+
/**
|
|
1844
|
+
* 逐块读取原始字节流并严格UTF-8解码;超限/取消后取消流并释放reader,清理失败不覆盖原异常。
|
|
1845
|
+
* 不要传入clone/tee分支,否则另一分支仍可能保留数据。
|
|
1846
|
+
* @param stream - 原始流;null返回空串但仍检查取消。
|
|
1847
|
+
* @param options - 字节预算与取消信号。
|
|
1848
|
+
* @returns 解码文本。
|
|
1849
|
+
* @throws RangeError预算无效或超限;非法UTF-8、流异常和signal.reason继续抛出。
|
|
1850
|
+
*
|
|
1851
|
+
* @example
|
|
1852
|
+
* ```ts
|
|
1853
|
+
* import { readTransportBody } from '@h-ai/crypto'
|
|
1854
|
+
* const request = new Request('https://api.example.com/api/v1/orders', { method: 'POST', body: 'hello' })
|
|
1855
|
+
* const text = await readTransportBody(request.body, { maxBytes: 1024, signal: request.signal })
|
|
1856
|
+
* ```
|
|
1857
|
+
*/
|
|
1858
|
+
declare function readTransportBody(stream: ReadableStream<Uint8Array> | null, options: ReadTransportBodyOptions): Promise<string>;
|
|
1859
|
+
|
|
1860
|
+
/**
|
|
1861
|
+
* @h-ai/crypto — 服务端受管传输密钥环的安全解析。
|
|
1862
|
+
* @module crypto-transport-keyring
|
|
1863
|
+
*/
|
|
1864
|
+
|
|
1865
|
+
/**
|
|
1866
|
+
* 解析服务端私有JSON或受管对象,不读取环境或初始化模块;实际密钥配对由createServer验证。
|
|
1867
|
+
* @param serialized - Secret Manager提供的JSON字符串或对象。
|
|
1868
|
+
* @returns 结构有效的密钥环或脱敏INVALID_KEY,不回显私钥/原始输入。
|
|
1869
|
+
*
|
|
1870
|
+
* @example
|
|
1871
|
+
* ```ts
|
|
1872
|
+
* import { parseTransportServerKeyRing, type TransportServerKeyRing } from '@h-ai/crypto'
|
|
1873
|
+
* const parsed = parseTransportServerKeyRing(process.env.HAI_CRYPTO_TRANSPORT_SERVERKEYRING)
|
|
1874
|
+
* if (!parsed.success) throw new Error(parsed.error.message)
|
|
1875
|
+
* const ring: TransportServerKeyRing = parsed.data
|
|
1876
|
+
* // 仅服务端使用;createServer还会校验每个版本的公私钥配对。
|
|
1877
|
+
* ```
|
|
1878
|
+
*/
|
|
1879
|
+
declare function parseTransportServerKeyRing(serialized: unknown): HaiResult<TransportServerKeyRing>;
|
|
1880
|
+
|
|
1881
|
+
/**
|
|
1882
|
+
* @h-ai/crypto — serv/kit 共用的实际 HTTP 请求认证入口。
|
|
1883
|
+
* @module crypto-transport-request
|
|
1884
|
+
*/
|
|
1885
|
+
|
|
1886
|
+
/**
|
|
1887
|
+
* 从实际HTTP请求提取可信方法/路径/查询/客户端ID并有界认证;成功后才执行业务。
|
|
1888
|
+
* 拒绝缺少认证及头/正文重复承载,空请求也认证;不会由payload自证HTTP上下文。
|
|
1889
|
+
* @param manager - 使用共享原子nonce registry的已创建管理器。
|
|
1890
|
+
* @param request - 未消费正文的真实Request。
|
|
1891
|
+
* @returns 明文和响应关联上下文;认证失败无明文。
|
|
1892
|
+
* @throws RangeError超限;流读取/取消沿用原异常,HTTP适配器负责转响应。
|
|
1893
|
+
*
|
|
1894
|
+
* @example
|
|
1895
|
+
* ```ts
|
|
1896
|
+
* import { authenticateTransportRequest, TRANSPORT_PROTOCOL, type TransportEncryptionManager } from '@h-ai/crypto'
|
|
1897
|
+
* async function handle(manager: TransportEncryptionManager, request: Request): Promise<Response> {
|
|
1898
|
+
* const verified = await authenticateTransportRequest(manager, request)
|
|
1899
|
+
* if (!verified.success) return new Response(null, { status: 400 })
|
|
1900
|
+
* const encrypted = await manager.encryptResponse(verified.data.context, JSON.stringify({ accepted: true }), 200)
|
|
1901
|
+
* if (!encrypted.success) return new Response(null, { status: 500 })
|
|
1902
|
+
* return Response.json(encrypted.data, { headers: { [TRANSPORT_PROTOCOL.ENCRYPTED_HEADER]: TRANSPORT_PROTOCOL.ENCRYPTED_HEADER_VALUE } })
|
|
1903
|
+
* }
|
|
1904
|
+
* ```
|
|
1905
|
+
*/
|
|
1906
|
+
declare function authenticateTransportRequest(manager: TransportEncryptionManager, request: Request): Promise<HaiResult<{
|
|
1907
|
+
plaintext: string;
|
|
1908
|
+
context: TransportMessageContext;
|
|
1909
|
+
}>>;
|
|
1910
|
+
|
|
586
1911
|
/**
|
|
587
1912
|
* @h-ai/crypto — 传输加密内存 key store
|
|
588
1913
|
*
|
|
589
|
-
*
|
|
1914
|
+
* 提供显式开发/测试使用的进程内客户端公钥存储,生产使用共享 registry。
|
|
590
1915
|
* @module crypto-transport-store-memory
|
|
591
1916
|
*/
|
|
592
1917
|
|
|
593
1918
|
/**
|
|
594
|
-
*
|
|
1919
|
+
* 显式创建开发/测试用进程内FIFO公钥registry;不跨进程、不持久化,生产共享状态守卫拒绝它。
|
|
1920
|
+
* 有效nonce不会因公钥FIFO容量而提前逐出;过期nonce惰性清理。
|
|
1921
|
+
* @param maxClients - 公钥容量,默认10000,超限淘汰最早公钥。
|
|
1922
|
+
* @returns 本地registry;无效SM2公钥拒绝Promise。
|
|
595
1923
|
*
|
|
596
|
-
*
|
|
597
|
-
*
|
|
598
|
-
*
|
|
599
|
-
*
|
|
600
|
-
*
|
|
1924
|
+
* @example
|
|
1925
|
+
* ```ts
|
|
1926
|
+
* import { crypto, createInMemoryKeyStore } from '@h-ai/crypto'
|
|
1927
|
+
* const initialized = await crypto.init()
|
|
1928
|
+
* if (!initialized.success) throw new Error(initialized.error.message)
|
|
1929
|
+
* try {
|
|
1930
|
+
* const generated = crypto.asymmetric.generateKeyPair()
|
|
1931
|
+
* if (!generated.success) throw new Error(generated.error.message)
|
|
1932
|
+
* const pair = generated.data
|
|
1933
|
+
* const registry = createInMemoryKeyStore(100)
|
|
1934
|
+
* const clientId = await registry.register(pair.publicKey)
|
|
1935
|
+
* const saved = await registry.get(clientId)
|
|
1936
|
+
* await registry.delete?.(clientId)
|
|
1937
|
+
* }
|
|
1938
|
+
* finally { await crypto.close() }
|
|
1939
|
+
* ```
|
|
601
1940
|
*/
|
|
602
1941
|
declare function createInMemoryKeyStore(maxClients?: number): TransportKeyStore;
|
|
603
1942
|
|
|
@@ -608,18 +1947,59 @@ declare function createInMemoryKeyStore(maxClients?: number): TransportKeyStore;
|
|
|
608
1947
|
* @module crypto-transport-store-redis
|
|
609
1948
|
*/
|
|
610
1949
|
|
|
611
|
-
/**
|
|
1950
|
+
/**
|
|
1951
|
+
* 借用已初始化cache的registry配置;只有Redis provider报告shared,内存cache不能满足生产。
|
|
1952
|
+
* TTL是客户端公钥寿命,nonce另按认证窗口持有;连接由应用关闭。
|
|
1953
|
+
*
|
|
1954
|
+
* @example
|
|
1955
|
+
* ```ts
|
|
1956
|
+
* import { crypto, createRedisTransportKeyStore, parseTransportServerKeyRing } from '@h-ai/crypto'
|
|
1957
|
+
* import { cache } from '@h-ai/cache'
|
|
1958
|
+
* const initialized = await crypto.init()
|
|
1959
|
+
* if (!initialized.success) throw new Error(initialized.error.message)
|
|
1960
|
+
* try {
|
|
1961
|
+
* const connected = await cache.init({ type: 'redis', host: 'cache', port: 6379, password: process.env.HAI_CACHE_PASSWORD })
|
|
1962
|
+
* if (!connected.success) throw new Error(connected.error.message)
|
|
1963
|
+
* try {
|
|
1964
|
+
* const registry = createRedisTransportKeyStore({ cache, ttlSeconds: 3600 })
|
|
1965
|
+
* const options: import('@h-ai/crypto').CreateRedisTransportKeyStoreOptions = { cache, ttlSeconds: 3600 }
|
|
1966
|
+
* const secondView = createRedisTransportKeyStore(options)
|
|
1967
|
+
* const scope = secondView.stateScope
|
|
1968
|
+
* }
|
|
1969
|
+
* finally { await cache.close() }
|
|
1970
|
+
* }
|
|
1971
|
+
* finally { await crypto.close() }
|
|
1972
|
+
* ```
|
|
1973
|
+
*/
|
|
612
1974
|
interface CreateRedisTransportKeyStoreOptions {
|
|
613
1975
|
/** 已初始化的 `@h-ai/cache` 实例;生产环境通常应使用 Redis provider。 */
|
|
614
1976
|
readonly cache: CacheFunctions;
|
|
615
|
-
/** 客户端公钥的 TTL
|
|
1977
|
+
/** 客户端公钥的 TTL(正整数秒,最大 2147483647);未传则保持不过期。 */
|
|
616
1978
|
readonly ttlSeconds?: number;
|
|
617
1979
|
}
|
|
618
1980
|
/**
|
|
619
|
-
*
|
|
1981
|
+
* 创建借用cache的公钥/nonce registry;生产必须Redis共享provider,nonce用单key原子CAS。
|
|
1982
|
+
* 公钥TTL省略则不过期,nonce有效期间禁止驱逐;容量由Redis部署预算管理,不提供DB maxClients。
|
|
1983
|
+
* @param options - 已初始化cache与可选正整数TTL秒,最大2147483647。
|
|
1984
|
+
* @returns registry;非法TTL同步TypeError,读写/公钥错误拒绝Promise,不关闭cache。
|
|
620
1985
|
*
|
|
621
|
-
*
|
|
622
|
-
*
|
|
1986
|
+
* @example
|
|
1987
|
+
* ```ts
|
|
1988
|
+
* import { crypto, createRedisTransportKeyStore, parseTransportServerKeyRing } from '@h-ai/crypto'
|
|
1989
|
+
* import { cache } from '@h-ai/cache'
|
|
1990
|
+
* const initialized = await crypto.init()
|
|
1991
|
+
* if (!initialized.success) throw new Error(initialized.error.message)
|
|
1992
|
+
* try {
|
|
1993
|
+
* const connected = await cache.init({ type: 'redis', host: 'cache', port: 6379, password: process.env.HAI_CACHE_PASSWORD })
|
|
1994
|
+
* if (!connected.success) throw new Error(connected.error.message)
|
|
1995
|
+
* try {
|
|
1996
|
+
* const registry = createRedisTransportKeyStore({ cache, ttlSeconds: 3600 })
|
|
1997
|
+
* const scope = registry.stateScope
|
|
1998
|
+
* }
|
|
1999
|
+
* finally { await cache.close() }
|
|
2000
|
+
* }
|
|
2001
|
+
* finally { await crypto.close() }
|
|
2002
|
+
* ```
|
|
623
2003
|
*/
|
|
624
2004
|
declare function createRedisTransportKeyStore(options: CreateRedisTransportKeyStoreOptions): TransportKeyStore;
|
|
625
2005
|
|
|
@@ -630,18 +2010,90 @@ declare function createRedisTransportKeyStore(options: CreateRedisTransportKeySt
|
|
|
630
2010
|
* @module crypto-transport-store-reldb
|
|
631
2011
|
*/
|
|
632
2012
|
|
|
633
|
-
/**
|
|
2013
|
+
/**
|
|
2014
|
+
* 借用已初始化数据库的registry配置;TTL与共享maxClients各节点必须一致。
|
|
2015
|
+
* 生产使用PostgreSQL/MySQL,DB时钟维护期限;连接由应用关闭。
|
|
2016
|
+
*
|
|
2017
|
+
* @example
|
|
2018
|
+
* ```ts
|
|
2019
|
+
* import { createReldbTransportKeyStore } from '@h-ai/crypto'
|
|
2020
|
+
* import { reldb } from '@h-ai/reldb'
|
|
2021
|
+
* const connected = await reldb.init({ type: 'postgresql', host: 'db', port: 5432, database: 'hai', user: 'postgres', password: process.env.HAI_DB_PASSWORD })
|
|
2022
|
+
* if (!connected.success) throw new Error(connected.error.message)
|
|
2023
|
+
* try {
|
|
2024
|
+
* const registry = createReldbTransportKeyStore({ reldb, ttlSeconds: 3600, maxClients: 10000 })
|
|
2025
|
+
* const options: import('@h-ai/crypto').CreateReldbTransportKeyStoreOptions = { reldb, ttlSeconds: 3600, maxClients: 10000 }
|
|
2026
|
+
* const secondView = createReldbTransportKeyStore(options)
|
|
2027
|
+
* const removed = await secondView.pruneExpired()
|
|
2028
|
+
* }
|
|
2029
|
+
* finally { await reldb.close() }
|
|
2030
|
+
* ```
|
|
2031
|
+
*/
|
|
634
2032
|
interface CreateReldbTransportKeyStoreOptions {
|
|
635
2033
|
/** 已初始化的 `@h-ai/reldb` 实例。 */
|
|
636
2034
|
readonly reldb: ReldbFunctions;
|
|
637
|
-
/**
|
|
2035
|
+
/** 公钥 TTL,正整数秒且最大 2147483647;省略则不过期。 */
|
|
638
2036
|
readonly ttlSeconds?: number;
|
|
2037
|
+
/** 所有节点共享的有效公钥容量,正安全整数,默认 10000;各节点必须一致。 */
|
|
2038
|
+
readonly maxClients?: number;
|
|
639
2039
|
}
|
|
640
2040
|
/**
|
|
641
|
-
*
|
|
2041
|
+
* 数据库registry及有界过期维护;注册自动维护,无流量时应用应定期调度pruneExpired。
|
|
2042
|
+
* 不淘汰有效公钥/nonce,不创建后台计时器;维护失败拒绝并可重试。
|
|
642
2043
|
*
|
|
643
|
-
*
|
|
2044
|
+
* @example
|
|
2045
|
+
* ```ts
|
|
2046
|
+
* import { createReldbTransportKeyStore } from '@h-ai/crypto'
|
|
2047
|
+
* import { reldb } from '@h-ai/reldb'
|
|
2048
|
+
* const connected = await reldb.init({ type: 'postgresql', host: 'db', port: 5432, database: 'hai', user: 'postgres', password: process.env.HAI_DB_PASSWORD })
|
|
2049
|
+
* if (!connected.success) throw new Error(connected.error.message)
|
|
2050
|
+
* try {
|
|
2051
|
+
* const registry = createReldbTransportKeyStore({ reldb, ttlSeconds: 3600, maxClients: 10000 })
|
|
2052
|
+
* const removed = await registry.pruneExpired()
|
|
2053
|
+
* }
|
|
2054
|
+
* finally { await reldb.close() }
|
|
2055
|
+
* ```
|
|
2056
|
+
*/
|
|
2057
|
+
interface ReldbTransportKeyStore extends TransportKeyStore {
|
|
2058
|
+
/**
|
|
2059
|
+
* 按数据库时钟最多删除 100 条过期公钥和 100 条过期 nonce,返回实际删除总数。
|
|
2060
|
+
* 不驱逐有效记录;建表或维护故障会 reject,之后允许重新调用。应用可以定期调度本方法。
|
|
2061
|
+
* @returns 异步操作结果;存储或网络故障按方法契约拒绝。
|
|
2062
|
+
*
|
|
2063
|
+
* @example
|
|
2064
|
+
* ```ts
|
|
2065
|
+
* import { createReldbTransportKeyStore } from '@h-ai/crypto'
|
|
2066
|
+
* import { reldb } from '@h-ai/reldb'
|
|
2067
|
+
* const connected = await reldb.init({ type: 'postgresql', host: 'db', port: 5432, database: 'hai', user: 'postgres', password: process.env.HAI_DB_PASSWORD })
|
|
2068
|
+
* if (!connected.success) throw new Error(connected.error.message)
|
|
2069
|
+
* try {
|
|
2070
|
+
* const registry = createReldbTransportKeyStore({ reldb, ttlSeconds: 3600, maxClients: 10000 })
|
|
2071
|
+
* const removed = await registry.pruneExpired()
|
|
2072
|
+
* }
|
|
2073
|
+
* finally { await reldb.close() }
|
|
2074
|
+
* ```
|
|
2075
|
+
*/
|
|
2076
|
+
pruneExpired: () => Promise<number>;
|
|
2077
|
+
}
|
|
2078
|
+
/**
|
|
2079
|
+
* 创建共享数据库registry;首次操作惰性建表,失败可重试,事务锁住持久容量行后注册。
|
|
2080
|
+
* 每次注册有界维护;公钥TTL与过期维护使用数据库epoch毫秒,传输窗口仍要求节点时钟同步,不能提前删除有效nonce。
|
|
2081
|
+
* @param options - 已初始化数据库、公钥TTL和各节点一致的正安全整数maxClients,默认10000。
|
|
2082
|
+
* @returns registry;非法配置同步TypeError,操作故障拒绝Promise;不关闭借用数据库。
|
|
2083
|
+
*
|
|
2084
|
+
* @example
|
|
2085
|
+
* ```ts
|
|
2086
|
+
* import { createReldbTransportKeyStore } from '@h-ai/crypto'
|
|
2087
|
+
* import { reldb } from '@h-ai/reldb'
|
|
2088
|
+
* const connected = await reldb.init({ type: 'postgresql', host: 'db', port: 5432, database: 'hai', user: 'postgres', password: process.env.HAI_DB_PASSWORD })
|
|
2089
|
+
* if (!connected.success) throw new Error(connected.error.message)
|
|
2090
|
+
* try {
|
|
2091
|
+
* const registry = createReldbTransportKeyStore({ reldb, ttlSeconds: 3600, maxClients: 10000 })
|
|
2092
|
+
* const removed = await registry.pruneExpired()
|
|
2093
|
+
* }
|
|
2094
|
+
* finally { await reldb.close() }
|
|
2095
|
+
* ```
|
|
644
2096
|
*/
|
|
645
|
-
declare function createReldbTransportKeyStore(options: CreateReldbTransportKeyStoreOptions):
|
|
2097
|
+
declare function createReldbTransportKeyStore(options: CreateReldbTransportKeyStoreOptions): ReldbTransportKeyStore;
|
|
646
2098
|
|
|
647
|
-
export { type AsymmetricEncryptOptions, type AsymmetricOperations, type CipherMode, type CreateRedisTransportKeyStoreOptions, type CreateReldbTransportKeyStoreOptions, type CryptoFunctions, type EncryptWithIVResult, type EncryptedPayload, HaiCryptoError, type HashOperations, type HashOptions, type KeyExchangeRequest, type KeyExchangeResponse, type KeyPair, type PasswordConfig, type PasswordOperations, type SignOptions, type SymmetricCbcEncryptedPayload, type SymmetricCiphertextEncoding, type SymmetricDecryptInput, type SymmetricEncryptOptions, type SymmetricEncryptedPayload, type SymmetricMode, type SymmetricOperations, TRANSPORT_PROTOCOL, type TransportClient, type TransportCreateServerOptions, type TransportCryptoServiceLike, type TransportEncryptionManager, type TransportKeyPair, type TransportKeyStore, type TransportOperations, createInMemoryKeyStore, createRedisTransportKeyStore, createReldbTransportKeyStore, crypto };
|
|
2099
|
+
export { type AsymmetricEncryptOptions, type AsymmetricOperations, type CipherMode, type CreateRedisTransportKeyStoreOptions, type CreateReldbTransportKeyStoreOptions, type CryptoFunctions, type EncryptWithIVResult, type EncryptedPayload, type EncryptedRequestPayload, HaiCryptoError, type HashOperations, type HashOptions, type KeyExchangeRequest, type KeyExchangeResponse, type KeyPair, type PasswordConfig, type PasswordOperations, type ReadTransportBodyOptions, type ReldbTransportKeyStore, type SignOptions, type SymmetricCbcEncryptedPayload, type SymmetricCiphertextEncoding, type SymmetricDecryptInput, type SymmetricEncryptOptions, type SymmetricEncryptedPayload, type SymmetricMode, type SymmetricOperations, TRANSPORT_BODY_LIMITS, TRANSPORT_PROTOCOL, type TransportClient, type TransportCreateServerOptions, type TransportCryptoServiceLike, type TransportEncryptionManager, type TransportKeyPair, type TransportKeyStore, type TransportMessageContext, type TransportOperations, type TransportRequestContext, type TransportServerKeyRing, authenticateTransportRequest, createInMemoryKeyStore, createRedisTransportKeyStore, createReldbTransportKeyStore, crypto, isValidSM2PublicKey, parseTransportServerKeyRing, readTransportBody };
|