@h-ai/crypto 0.1.0-alpha5

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/dist/index.js ADDED
@@ -0,0 +1,820 @@
1
+ import { core, ok, err } from '@h-ai/core';
2
+ import smCrypto from 'sm-crypto';
3
+
4
+ // src/crypto-main.ts
5
+
6
+ // messages/en-US.json
7
+ var en_US_default = {
8
+ $schema: "https://inlang.com/schema/inlang-message-format",
9
+ crypto_notInitialized: "Crypto module not initialized, call crypto.init() first",
10
+ crypto_initFailed: "Crypto module initialization failed: {error}",
11
+ crypto_sm2PublicKeyInvalid: "Invalid SM2 public key format",
12
+ crypto_sm2PrivateKeyInvalid: "Invalid SM2 private key format",
13
+ crypto_sm2EncryptEmpty: "SM2 encryption returned empty result",
14
+ crypto_sm2DecryptFailed: "SM2 decryption failed or returned invalid result",
15
+ crypto_sm2SignEmpty: "SM2 signature returned empty result",
16
+ crypto_sm2KeyPairGenerateFailed: "SM2 key pair generation failed: {error}",
17
+ crypto_sm2EncryptFailed: "SM2 encryption failed: {error}",
18
+ crypto_sm2DecryptFailedWithError: "SM2 decryption failed: {error}",
19
+ crypto_sm2SignFailed: "SM2 signing failed: {error}",
20
+ crypto_sm2VerifyFailed: "SM2 verification failed: {error}",
21
+ crypto_sm3HashEmpty: "SM3 hash returned empty result",
22
+ crypto_sm3HashFailed: "SM3 hash calculation failed: {error}",
23
+ crypto_sm3HmacFailed: "HMAC-SM3 calculation failed: {error}",
24
+ crypto_sm3VerifyFailed: "SM3 hash verification failed: {error}",
25
+ crypto_sm4KeyInvalid: "SM4 key must be 16 bytes (32 hex characters)",
26
+ crypto_sm4CbcNeedIv: "CBC mode requires IV",
27
+ crypto_sm4IvInvalid: "SM4 IV must be 16 bytes (32 hex characters)",
28
+ crypto_sm4EncryptEmpty: "SM4 encryption returned empty result",
29
+ crypto_sm4DecryptFailed: "SM4 decryption failed",
30
+ crypto_sm4EncryptFailed: "SM4 encryption failed: {error}",
31
+ crypto_sm4DecryptFailedWithError: "SM4 decryption failed: {error}",
32
+ crypto_passwordEmpty: "Password cannot be empty",
33
+ crypto_passwordHashEmpty: "Password and hash cannot be empty",
34
+ crypto_passwordHashFailed: "Password hashing failed",
35
+ crypto_hashFormatInvalid: "Invalid hash format",
36
+ crypto_passwordVerifyFailed: "Password verification failed"
37
+ };
38
+
39
+ // messages/zh-CN.json
40
+ var zh_CN_default = {
41
+ $schema: "https://inlang.com/schema/inlang-message-format",
42
+ crypto_notInitialized: "\u52A0\u5BC6\u6A21\u5757\u5C1A\u672A\u521D\u59CB\u5316\uFF0C\u8BF7\u5148\u8C03\u7528 crypto.init()",
43
+ crypto_initFailed: "\u52A0\u5BC6\u6A21\u5757\u521D\u59CB\u5316\u5931\u8D25\uFF1A{error}",
44
+ crypto_sm2PublicKeyInvalid: "\u65E0\u6548\u7684 SM2 \u516C\u94A5\u683C\u5F0F",
45
+ crypto_sm2PrivateKeyInvalid: "\u65E0\u6548\u7684 SM2 \u79C1\u94A5\u683C\u5F0F",
46
+ crypto_sm2EncryptEmpty: "SM2 \u52A0\u5BC6\u8FD4\u56DE\u7A7A\u7ED3\u679C",
47
+ crypto_sm2DecryptFailed: "SM2 \u89E3\u5BC6\u5931\u8D25\u6216\u8FD4\u56DE\u65E0\u6548\u7ED3\u679C",
48
+ crypto_sm2SignEmpty: "SM2 \u7B7E\u540D\u8FD4\u56DE\u7A7A\u7ED3\u679C",
49
+ crypto_sm2KeyPairGenerateFailed: "SM2 \u5BC6\u94A5\u5BF9\u751F\u6210\u5931\u8D25: {error}",
50
+ crypto_sm2EncryptFailed: "SM2 \u52A0\u5BC6\u5931\u8D25: {error}",
51
+ crypto_sm2DecryptFailedWithError: "SM2 \u89E3\u5BC6\u5931\u8D25: {error}",
52
+ crypto_sm2SignFailed: "SM2 \u7B7E\u540D\u5931\u8D25: {error}",
53
+ crypto_sm2VerifyFailed: "SM2 \u9A8C\u7B7E\u5931\u8D25: {error}",
54
+ crypto_sm3HashEmpty: "SM3 \u54C8\u5E0C\u8FD4\u56DE\u7A7A\u7ED3\u679C",
55
+ crypto_sm3HashFailed: "SM3 \u54C8\u5E0C\u8BA1\u7B97\u5931\u8D25: {error}",
56
+ crypto_sm3HmacFailed: "HMAC-SM3 \u8BA1\u7B97\u5931\u8D25: {error}",
57
+ crypto_sm3VerifyFailed: "SM3 \u54C8\u5E0C\u9A8C\u8BC1\u5931\u8D25: {error}",
58
+ crypto_sm4KeyInvalid: "SM4 \u5BC6\u94A5\u5FC5\u987B\u4E3A 16 \u5B57\u8282\uFF0832 \u4E2A\u5341\u516D\u8FDB\u5236\u5B57\u7B26\uFF09",
59
+ crypto_sm4CbcNeedIv: "CBC \u6A21\u5F0F\u5FC5\u987B\u63D0\u4F9B IV",
60
+ crypto_sm4IvInvalid: "SM4 IV \u5FC5\u987B\u4E3A 16 \u5B57\u8282\uFF0832 \u4E2A\u5341\u516D\u8FDB\u5236\u5B57\u7B26\uFF09",
61
+ crypto_sm4EncryptEmpty: "SM4 \u52A0\u5BC6\u8FD4\u56DE\u7A7A\u7ED3\u679C",
62
+ crypto_sm4DecryptFailed: "SM4 \u89E3\u5BC6\u5931\u8D25",
63
+ crypto_sm4EncryptFailed: "SM4 \u52A0\u5BC6\u5931\u8D25: {error}",
64
+ crypto_sm4DecryptFailedWithError: "SM4 \u89E3\u5BC6\u5931\u8D25: {error}",
65
+ crypto_passwordEmpty: "\u5BC6\u7801\u4E0D\u80FD\u4E3A\u7A7A",
66
+ crypto_passwordHashEmpty: "\u5BC6\u7801\u548C\u54C8\u5E0C\u503C\u4E0D\u80FD\u4E3A\u7A7A",
67
+ crypto_passwordHashFailed: "\u5BC6\u7801\u54C8\u5E0C\u5931\u8D25",
68
+ crypto_hashFormatInvalid: "\u65E0\u6548\u7684\u54C8\u5E0C\u683C\u5F0F",
69
+ crypto_passwordVerifyFailed: "\u5BC6\u7801\u9A8C\u8BC1\u5931\u8D25"
70
+ };
71
+
72
+ // src/crypto-i18n.ts
73
+ var cryptoM = core.i18n.createMessageGetter({
74
+ "zh-CN": zh_CN_default,
75
+ "en-US": en_US_default
76
+ });
77
+ var CryptoErrorInfo = {
78
+ INVALID_INPUT: "002:400",
79
+ INVALID_KEY: "003:400",
80
+ NOT_INITIALIZED: "010:500",
81
+ INIT_FAILED: "011:500",
82
+ KEY_GENERATION_FAILED: "020:500",
83
+ ENCRYPTION_FAILED: "021:500",
84
+ DECRYPTION_FAILED: "022:500",
85
+ SIGN_FAILED: "023:500",
86
+ VERIFY_FAILED: "024:500",
87
+ HASH_FAILED: "040:500",
88
+ HMAC_FAILED: "041:500",
89
+ INVALID_IV: "060:400"
90
+ };
91
+ var HaiCryptoError = core.error.buildHaiErrorsDef("crypto", CryptoErrorInfo);
92
+
93
+ // src/crypto-password.ts
94
+ function generateSalt(length) {
95
+ const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
96
+ const randomBytes = new Uint8Array(length);
97
+ globalThis.crypto.getRandomValues(randomBytes);
98
+ let salt = "";
99
+ for (let i = 0; i < length; i++) {
100
+ salt += chars.charAt(randomBytes[i] % chars.length);
101
+ }
102
+ return salt;
103
+ }
104
+ function iterateHash(hash, data, salt, iterations) {
105
+ let current = salt + data;
106
+ for (let i = 0; i < iterations; i++) {
107
+ const result = hash.hash(current);
108
+ if (!result.success) {
109
+ return result;
110
+ }
111
+ current = result.data;
112
+ }
113
+ return ok(current);
114
+ }
115
+ function createPasswordFunctions(deps) {
116
+ const { hash: hashOps } = deps;
117
+ return {
118
+ /**
119
+ * 对密码进行迭代加盐哈希
120
+ *
121
+ * 输出格式: `$hai$<iterations>$<salt>$<hash>`
122
+ *
123
+ * @param password - 明文密码(不能为空)
124
+ * @param config - 可选配置(盐值长度、迭代次数)
125
+ * @returns 成功时返回格式化的哈希字符串;失败时返回 INVALID_INPUT 或 HASH_FAILED
126
+ */
127
+ hash(password, config = {}) {
128
+ const { saltLength = 16, iterations = 1e4 } = config;
129
+ try {
130
+ if (!password) {
131
+ return err(
132
+ HaiCryptoError.INVALID_INPUT,
133
+ cryptoM("crypto_passwordEmpty")
134
+ );
135
+ }
136
+ const salt = generateSalt(saltLength);
137
+ const hashResult = iterateHash(hashOps, password, salt, iterations);
138
+ if (!hashResult.success) {
139
+ return hashResult;
140
+ }
141
+ const formatted = `$hai$${iterations}$${salt}$${hashResult.data}`;
142
+ return ok(formatted);
143
+ } catch (error) {
144
+ return err(
145
+ HaiCryptoError.HASH_FAILED,
146
+ cryptoM("crypto_passwordHashFailed"),
147
+ error
148
+ );
149
+ }
150
+ },
151
+ /**
152
+ * 验证密码是否匹配已存储的哈希
153
+ *
154
+ * 从哈希字符串中解析迭代次数和盐值,重新计算后比较。
155
+ * 格式要求: `$hai$<iterations>$<salt>$<hash>`
156
+ *
157
+ * @param password - 待验证的明文密码
158
+ * @param hash - 存储的哈希值
159
+ * @returns 成功时返回 boolean;失败时返回 INVALID_INPUT 或 VERIFY_FAILED
160
+ */
161
+ verify(password, hash) {
162
+ try {
163
+ if (!password || !hash) {
164
+ return err(
165
+ HaiCryptoError.INVALID_INPUT,
166
+ cryptoM("crypto_passwordHashEmpty")
167
+ );
168
+ }
169
+ const parts = hash.split("$");
170
+ if (parts.length !== 5 || parts[1] !== "hai") {
171
+ return err(
172
+ HaiCryptoError.INVALID_INPUT,
173
+ cryptoM("crypto_hashFormatInvalid")
174
+ );
175
+ }
176
+ const storedIterations = Number.parseInt(parts[2], 10);
177
+ const salt = parts[3];
178
+ const storedHash = parts[4];
179
+ if (Number.isNaN(storedIterations) || !salt || !storedHash) {
180
+ return err(
181
+ HaiCryptoError.INVALID_INPUT,
182
+ cryptoM("crypto_hashFormatInvalid")
183
+ );
184
+ }
185
+ const hashResult = iterateHash(hashOps, password, salt, storedIterations);
186
+ if (!hashResult.success) {
187
+ return hashResult;
188
+ }
189
+ return ok(core.string.constantTimeEqual(hashResult.data, storedHash));
190
+ } catch (error) {
191
+ return err(
192
+ HaiCryptoError.VERIFY_FAILED,
193
+ cryptoM("crypto_passwordVerifyFailed"),
194
+ error
195
+ );
196
+ }
197
+ }
198
+ };
199
+ }
200
+
201
+ // src/crypto-utils.ts
202
+ function isBase64(str) {
203
+ return str.includes("+") || str.includes("/") || str.endsWith("=");
204
+ }
205
+ function hexToBase64(hex) {
206
+ const bytes = new Uint8Array(hex.length / 2);
207
+ for (let i = 0; i < hex.length; i += 2) {
208
+ bytes[i / 2] = Number.parseInt(hex.slice(i, i + 2), 16);
209
+ }
210
+ return btoa(String.fromCharCode(...bytes));
211
+ }
212
+ function base64ToHex(base64) {
213
+ const binary = atob(base64);
214
+ const bytes = new Uint8Array(binary.length);
215
+ for (let i = 0; i < binary.length; i++) {
216
+ bytes[i] = binary.charCodeAt(i);
217
+ }
218
+ return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
219
+ }
220
+
221
+ // src/crypto-sm2.ts
222
+ var { sm2 } = smCrypto;
223
+ function createSM2() {
224
+ return {
225
+ /**
226
+ * 生成 SM2 密钥对
227
+ *
228
+ * @returns 成功时返回包含公钥(130 字符含 04 前缀)和私钥(64 字符)的密钥对
229
+ */
230
+ generateKeyPair() {
231
+ try {
232
+ const keyPair = sm2.generateKeyPairHex();
233
+ return ok({
234
+ publicKey: keyPair.publicKey,
235
+ privateKey: keyPair.privateKey
236
+ });
237
+ } catch (error) {
238
+ return err(
239
+ HaiCryptoError.KEY_GENERATION_FAILED,
240
+ cryptoM("crypto_sm2KeyPairGenerateFailed", { params: { error: error instanceof Error ? error.message : String(error) } }),
241
+ error
242
+ );
243
+ }
244
+ },
245
+ /**
246
+ * SM2 非对称加密
247
+ *
248
+ * 公钥自动补齐 04 前缀;支持 hex/base64 输出。
249
+ *
250
+ * @param data - 待加密明文
251
+ * @param publicKey - 公钥(支持带/不带 04 前缀)
252
+ * @param options - 加密选项(密文模式、输出格式)
253
+ * @returns 成功时返回密文;失败时返回 INVALID_KEY 或 ENCRYPTION_FAILED
254
+ */
255
+ encrypt(data, publicKey, options = {}) {
256
+ const { cipherMode = 1, outputFormat = "hex" } = options;
257
+ if (!this.isValidPublicKey(publicKey)) {
258
+ return err(
259
+ HaiCryptoError.INVALID_KEY,
260
+ cryptoM("crypto_sm2PublicKeyInvalid")
261
+ );
262
+ }
263
+ try {
264
+ const key = publicKey.startsWith("04") ? publicKey : `04${publicKey}`;
265
+ const encrypted = sm2.doEncrypt(data, key, cipherMode);
266
+ if (!encrypted) {
267
+ return err(
268
+ HaiCryptoError.ENCRYPTION_FAILED,
269
+ cryptoM("crypto_sm2EncryptEmpty")
270
+ );
271
+ }
272
+ if (outputFormat === "base64") {
273
+ return ok(hexToBase64(encrypted));
274
+ }
275
+ return ok(encrypted);
276
+ } catch (error) {
277
+ return err(
278
+ HaiCryptoError.ENCRYPTION_FAILED,
279
+ cryptoM("crypto_sm2EncryptFailed", { params: { error: error instanceof Error ? error.message : String(error) } }),
280
+ error
281
+ );
282
+ }
283
+ },
284
+ /**
285
+ * SM2 非对称解密
286
+ *
287
+ * 自动检测 base64 格式输入并转换为 hex 后解密。
288
+ *
289
+ * @param ciphertext - 密文(hex 或 base64)
290
+ * @param privateKey - 私钥(64 字符十六进制)
291
+ * @param options - 解密选项(密文模式需与加密时一致)
292
+ * @returns 成功时返回明文;失败时返回 INVALID_KEY 或 DECRYPTION_FAILED
293
+ */
294
+ decrypt(ciphertext, privateKey, options = {}) {
295
+ const { cipherMode = 1 } = options;
296
+ if (!this.isValidPrivateKey(privateKey)) {
297
+ return err(
298
+ HaiCryptoError.INVALID_KEY,
299
+ cryptoM("crypto_sm2PrivateKeyInvalid")
300
+ );
301
+ }
302
+ try {
303
+ let input = ciphertext;
304
+ if (isBase64(ciphertext)) {
305
+ input = base64ToHex(ciphertext);
306
+ }
307
+ const decrypted = sm2.doDecrypt(input, privateKey, cipherMode);
308
+ if (decrypted === false || decrypted === null || decrypted === void 0) {
309
+ return err(
310
+ HaiCryptoError.DECRYPTION_FAILED,
311
+ cryptoM("crypto_sm2DecryptFailed")
312
+ );
313
+ }
314
+ return ok(decrypted);
315
+ } catch (error) {
316
+ return err(
317
+ HaiCryptoError.DECRYPTION_FAILED,
318
+ cryptoM("crypto_sm2DecryptFailedWithError", { params: { error: error instanceof Error ? error.message : String(error) } }),
319
+ error
320
+ );
321
+ }
322
+ },
323
+ /**
324
+ * SM2 数字签名
325
+ *
326
+ * 默认对数据先做哈希(hash=true),使用 userId 作为签名附加参数。
327
+ *
328
+ * @param data - 待签名数据
329
+ * @param privateKey - 私钥(64 字符十六进制)
330
+ * @param options - 签名选项(hash 开关、userId)
331
+ * @returns 成功时返回签名字符串;失败时返回 INVALID_KEY 或 SIGN_FAILED
332
+ */
333
+ sign(data, privateKey, options = {}) {
334
+ const { hash = true, userId = "1234567812345678" } = options;
335
+ if (!this.isValidPrivateKey(privateKey)) {
336
+ return err(
337
+ HaiCryptoError.INVALID_KEY,
338
+ cryptoM("crypto_sm2PrivateKeyInvalid")
339
+ );
340
+ }
341
+ try {
342
+ const signature = sm2.doSignature(data, privateKey, { hash, userId });
343
+ if (!signature) {
344
+ return err(
345
+ HaiCryptoError.SIGN_FAILED,
346
+ cryptoM("crypto_sm2SignEmpty")
347
+ );
348
+ }
349
+ return ok(signature);
350
+ } catch (error) {
351
+ return err(
352
+ HaiCryptoError.SIGN_FAILED,
353
+ cryptoM("crypto_sm2SignFailed", { params: { error: error instanceof Error ? error.message : String(error) } }),
354
+ error
355
+ );
356
+ }
357
+ },
358
+ /**
359
+ * SM2 签名验证
360
+ *
361
+ * 公钥自动补齐 04 前缀;hash/userId 需与签名时一致。
362
+ *
363
+ * @param data - 原始数据
364
+ * @param signature - 签名值
365
+ * @param publicKey - 公钥(支持带/不带 04 前缀)
366
+ * @param options - 验签选项(hash 开关、userId)
367
+ * @returns 成功时返回 boolean;失败时返回 INVALID_KEY 或 VERIFY_FAILED
368
+ */
369
+ verify(data, signature, publicKey, options = {}) {
370
+ const { hash = true, userId = "1234567812345678" } = options;
371
+ if (!this.isValidPublicKey(publicKey)) {
372
+ return err(
373
+ HaiCryptoError.INVALID_KEY,
374
+ cryptoM("crypto_sm2PublicKeyInvalid")
375
+ );
376
+ }
377
+ try {
378
+ const key = publicKey.startsWith("04") ? publicKey : `04${publicKey}`;
379
+ const isValid = sm2.doVerifySignature(data, signature, key, { hash, userId });
380
+ return ok(!!isValid);
381
+ } catch (error) {
382
+ return err(
383
+ HaiCryptoError.VERIFY_FAILED,
384
+ cryptoM("crypto_sm2VerifyFailed", { params: { error: error instanceof Error ? error.message : String(error) } }),
385
+ error
386
+ );
387
+ }
388
+ },
389
+ /**
390
+ * 校验公钥格式是否合法
391
+ *
392
+ * 合法格式:128 字符十六进制(无前缀)或 130 字符(含 04 前缀)。
393
+ *
394
+ * @param key - 待校验公钥
395
+ * @returns 格式合法返回 true
396
+ */
397
+ isValidPublicKey(key) {
398
+ if (!key || typeof key !== "string")
399
+ return false;
400
+ const cleanKey = key.startsWith("04") ? key.slice(2) : key;
401
+ return /^[0-9a-f]{128}$/i.test(cleanKey);
402
+ },
403
+ /**
404
+ * 校验私钥格式是否合法
405
+ *
406
+ * 合法格式:64 字符十六进制。
407
+ *
408
+ * @param key - 待校验私钥
409
+ * @returns 格式合法返回 true
410
+ */
411
+ isValidPrivateKey(key) {
412
+ if (!key || typeof key !== "string")
413
+ return false;
414
+ return /^[0-9a-f]{64}$/i.test(key);
415
+ }
416
+ };
417
+ }
418
+ var { sm3 } = smCrypto;
419
+ function createSM3() {
420
+ return {
421
+ /**
422
+ * 计算 SM3 哈希
423
+ *
424
+ * 支持字符串(UTF-8/Hex)和 Uint8Array 输入。
425
+ *
426
+ * @param data - 待哈希数据
427
+ * @param options - 输入编码与输出格式
428
+ * @returns 成功时返回 64 字符十六进制哈希值;失败时返回 HASH_FAILED
429
+ */
430
+ hash(data, options = {}) {
431
+ const { inputEncoding = "utf8" } = options;
432
+ try {
433
+ let input;
434
+ if (data instanceof Uint8Array) {
435
+ input = Array.from(data);
436
+ } else if (inputEncoding === "hex") {
437
+ input = hexToBytes(data);
438
+ } else {
439
+ input = data;
440
+ }
441
+ const result = sm3(input);
442
+ if (!result) {
443
+ return err(
444
+ HaiCryptoError.HASH_FAILED,
445
+ cryptoM("crypto_sm3HashEmpty")
446
+ );
447
+ }
448
+ return ok(result);
449
+ } catch (error) {
450
+ return err(
451
+ HaiCryptoError.HASH_FAILED,
452
+ cryptoM("crypto_sm3HashFailed", { params: { error: error instanceof Error ? error.message : String(error) } }),
453
+ error
454
+ );
455
+ }
456
+ },
457
+ /**
458
+ * 计算 HMAC-SM3 消息认证码
459
+ *
460
+ * 实现遵循 RFC 2104;密钥超过块大小(64 字节)时先对密钥做哈希。
461
+ *
462
+ * @param data - 待计算数据
463
+ * @param key - HMAC 密钥
464
+ * @returns 成功时返回 64 字符十六进制 HMAC 值;失败时返回 HMAC_FAILED
465
+ */
466
+ hmac(data, key) {
467
+ try {
468
+ const blockSize = 64;
469
+ const opad = 92;
470
+ const ipad = 54;
471
+ let keyBytes;
472
+ if (key.length > blockSize) {
473
+ const hashedKey = sm3(key);
474
+ keyBytes = hexToBytes(hashedKey);
475
+ } else {
476
+ keyBytes = stringToBytes(key);
477
+ }
478
+ while (keyBytes.length < blockSize) {
479
+ keyBytes.push(0);
480
+ }
481
+ const iKeyPad = keyBytes.map((b) => b ^ ipad);
482
+ const oKeyPad = keyBytes.map((b) => b ^ opad);
483
+ const innerInput = iKeyPad.concat(stringToBytes(data));
484
+ const innerHash = sm3(innerInput);
485
+ const outerInput = oKeyPad.concat(hexToBytes(innerHash));
486
+ const result = sm3(outerInput);
487
+ return ok(result);
488
+ } catch (error) {
489
+ return err(
490
+ HaiCryptoError.HMAC_FAILED,
491
+ cryptoM("crypto_sm3HmacFailed", { params: { error: error instanceof Error ? error.message : String(error) } }),
492
+ error
493
+ );
494
+ }
495
+ },
496
+ /**
497
+ * 验证数据的哈希是否匹配
498
+ *
499
+ * 对数据做 SM3 哈希后与期望值比较(忽略大小写)。
500
+ *
501
+ * @param data - 原始数据
502
+ * @param expectedHash - 期望的哈希值
503
+ * @returns 成功时返回 boolean;失败时返回 HASH_FAILED
504
+ */
505
+ verify(data, expectedHash) {
506
+ try {
507
+ const hashResult = sm3(data);
508
+ return ok(core.string.constantTimeEqual(hashResult.toLowerCase(), expectedHash.toLowerCase()));
509
+ } catch (error) {
510
+ return err(
511
+ HaiCryptoError.HASH_FAILED,
512
+ cryptoM("crypto_sm3VerifyFailed", { params: { error: error instanceof Error ? error.message : String(error) } }),
513
+ error
514
+ );
515
+ }
516
+ }
517
+ };
518
+ }
519
+ function hexToBytes(hex) {
520
+ const bytes = [];
521
+ for (let i = 0; i < hex.length; i += 2) {
522
+ bytes.push(Number.parseInt(hex.slice(i, i + 2), 16));
523
+ }
524
+ return bytes;
525
+ }
526
+ function stringToBytes(str) {
527
+ const encoder = new TextEncoder();
528
+ return Array.from(encoder.encode(str));
529
+ }
530
+ var { sm3: sm32, sm4 } = smCrypto;
531
+ function createSM4() {
532
+ return {
533
+ /** 生成随机密钥(16 字节 = 32 个十六进制字符) */
534
+ generateKey() {
535
+ return generateRandomHex(16);
536
+ },
537
+ /** 生成随机 IV(16 字节 = 32 个十六进制字符) */
538
+ generateIV() {
539
+ return generateRandomHex(16);
540
+ },
541
+ /**
542
+ * SM4 对称加密
543
+ *
544
+ * 支持 ECB(默认)和 CBC 两种模式,使用 PKCS#7 填充。
545
+ * CBC 模式需提供合法 IV。
546
+ *
547
+ * @param data - 待加密明文
548
+ * @param key - 密钥(32 字符十六进制)
549
+ * @param options - 加密模式/IV/输出格式
550
+ * @returns 成功时返回密文;失败时返回 INVALID_KEY/INVALID_IV/ENCRYPTION_FAILED
551
+ */
552
+ encrypt(data, key, options = {}) {
553
+ const {
554
+ mode = "ecb",
555
+ iv,
556
+ outputFormat = "hex"
557
+ } = options;
558
+ if (!this.isValidKey(key)) {
559
+ return err(
560
+ HaiCryptoError.INVALID_KEY,
561
+ cryptoM("crypto_sm4KeyInvalid")
562
+ );
563
+ }
564
+ if (mode === "cbc" && !iv) {
565
+ return err(
566
+ HaiCryptoError.INVALID_IV,
567
+ cryptoM("crypto_sm4CbcNeedIv")
568
+ );
569
+ }
570
+ if (mode === "cbc" && iv && !this.isValidIV(iv)) {
571
+ return err(
572
+ HaiCryptoError.INVALID_IV,
573
+ cryptoM("crypto_sm4IvInvalid")
574
+ );
575
+ }
576
+ try {
577
+ const sm4Options = {
578
+ mode,
579
+ padding: "pkcs#7"
580
+ };
581
+ if (mode === "cbc" && iv) {
582
+ sm4Options.iv = iv;
583
+ }
584
+ const encrypted = sm4.encrypt(data, key, sm4Options);
585
+ if (!encrypted) {
586
+ return err(
587
+ HaiCryptoError.ENCRYPTION_FAILED,
588
+ cryptoM("crypto_sm4EncryptEmpty")
589
+ );
590
+ }
591
+ if (outputFormat === "base64") {
592
+ return ok(hexToBase64(encrypted));
593
+ }
594
+ return ok(encrypted);
595
+ } catch (error) {
596
+ return err(
597
+ HaiCryptoError.ENCRYPTION_FAILED,
598
+ cryptoM("crypto_sm4EncryptFailed", { params: { error: error instanceof Error ? error.message : String(error) } }),
599
+ error
600
+ );
601
+ }
602
+ },
603
+ /**
604
+ * SM4 对称解密
605
+ *
606
+ * 自动检测 base64 格式输入并转换为 hex。
607
+ * 解密模式和 IV 需与加密时一致。
608
+ *
609
+ * @param ciphertext - 密文(hex 或 base64)
610
+ * @param key - 密钥(32 字符十六进制)
611
+ * @param options - 解密模式/IV
612
+ * @returns 成功时返回明文;失败时返回 INVALID_KEY/INVALID_IV/DECRYPTION_FAILED
613
+ */
614
+ decrypt(ciphertext, key, options = {}) {
615
+ const { mode = "ecb", iv } = options;
616
+ if (!this.isValidKey(key)) {
617
+ return err(
618
+ HaiCryptoError.INVALID_KEY,
619
+ cryptoM("crypto_sm4KeyInvalid")
620
+ );
621
+ }
622
+ if (mode === "cbc" && !iv) {
623
+ return err(
624
+ HaiCryptoError.INVALID_IV,
625
+ cryptoM("crypto_sm4CbcNeedIv")
626
+ );
627
+ }
628
+ try {
629
+ let input = ciphertext;
630
+ if (isBase64(ciphertext)) {
631
+ input = base64ToHex(ciphertext);
632
+ }
633
+ const sm4Options = {
634
+ mode,
635
+ padding: "pkcs#7"
636
+ };
637
+ if (mode === "cbc" && iv) {
638
+ sm4Options.iv = iv;
639
+ }
640
+ const decrypted = sm4.decrypt(input, key, sm4Options);
641
+ if (decrypted === false || decrypted === null || decrypted === void 0) {
642
+ return err(
643
+ HaiCryptoError.DECRYPTION_FAILED,
644
+ cryptoM("crypto_sm4DecryptFailed")
645
+ );
646
+ }
647
+ return ok(decrypted);
648
+ } catch (error) {
649
+ return err(
650
+ HaiCryptoError.DECRYPTION_FAILED,
651
+ cryptoM("crypto_sm4DecryptFailedWithError", { params: { error: error instanceof Error ? error.message : String(error) } }),
652
+ error
653
+ );
654
+ }
655
+ },
656
+ /**
657
+ * 带 IV 加密(CBC 模式,自动生成随机 IV)
658
+ *
659
+ * @param data - 待加密明文
660
+ * @param key - 密钥(32 字符十六进制)
661
+ * @returns 成功时返回 { ciphertext, iv };失败时同 encrypt
662
+ */
663
+ encryptWithIV(data, key) {
664
+ const iv = this.generateIV();
665
+ const result = this.encrypt(data, key, { mode: "cbc", iv });
666
+ if (!result.success) {
667
+ return result;
668
+ }
669
+ return ok({ ciphertext: result.data, iv });
670
+ },
671
+ /**
672
+ * 带 IV 解密(CBC 模式)
673
+ *
674
+ * @param ciphertext - 密文
675
+ * @param key - 密钥
676
+ * @param iv - 加密时使用的 IV
677
+ * @returns 成功时返回明文;失败时同 decrypt
678
+ */
679
+ decryptWithIV(ciphertext, key, iv) {
680
+ return this.decrypt(ciphertext, key, { mode: "cbc", iv });
681
+ },
682
+ /**
683
+ * 从密码和盐值派生密钥
684
+ *
685
+ * 内部使用 SM3 哈希(password + salt) 取前 32 字符作为密钥。
686
+ * 注意:此为简单派生,不适用于高安全场景。
687
+ *
688
+ * @param password - 密码
689
+ * @param salt - 盐值
690
+ * @returns 32 字符十六进制密钥
691
+ */
692
+ deriveKey(password, salt) {
693
+ const combined = password + salt;
694
+ const hash = sm32(combined);
695
+ return hash.slice(0, 32);
696
+ },
697
+ /** 校验密钥格式是否合法(32 字符十六进制) */
698
+ isValidKey(key) {
699
+ return /^[0-9a-f]{32}$/i.test(key);
700
+ },
701
+ /** 校验 IV 格式是否合法(32 字符十六进制) */
702
+ isValidIV(iv) {
703
+ return /^[0-9a-f]{32}$/i.test(iv);
704
+ }
705
+ };
706
+ }
707
+ function generateRandomHex(byteLength) {
708
+ const bytes = new Uint8Array(byteLength);
709
+ crypto.getRandomValues(bytes);
710
+ return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
711
+ }
712
+
713
+ // src/crypto-main.ts
714
+ var logger = core.logger.child({ module: "crypto", scope: "main" });
715
+ var initialized = false;
716
+ var initInProgress = false;
717
+ var currentAsymmetric = null;
718
+ var currentHash = null;
719
+ var currentSymmetric = null;
720
+ var currentPassword = null;
721
+ var notInitialized = core.module.createNotInitializedKit(
722
+ HaiCryptoError.NOT_INITIALIZED,
723
+ () => cryptoM("crypto_notInitialized")
724
+ );
725
+ var notInitializedAsymmetric = notInitialized.proxy("sync");
726
+ var notInitializedHash = notInitialized.proxy("sync");
727
+ var notInitializedSymmetric = notInitialized.proxy("sync");
728
+ var notInitializedPassword = notInitialized.proxy("sync");
729
+ var crypto2 = {
730
+ /**
731
+ * 初始化加密模块
732
+ *
733
+ * 创建非对称/哈希/对称/密码哈希操作实例。
734
+ * 重复调用会先关闭再重新初始化。
735
+ *
736
+ * @returns 成功时返回 ok(undefined);失败时返回 INIT_FAILED
737
+ */
738
+ async init() {
739
+ if (initInProgress) {
740
+ logger.warn("Crypto init already in progress, skipping concurrent call");
741
+ return err(
742
+ HaiCryptoError.INIT_FAILED,
743
+ cryptoM("crypto_initFailed", { params: { error: "Concurrent initialization detected" } })
744
+ );
745
+ }
746
+ initInProgress = true;
747
+ try {
748
+ if (initialized) {
749
+ logger.warn("Crypto module is already initialized, reinitializing");
750
+ await crypto2.close();
751
+ }
752
+ logger.info("Initializing crypto module");
753
+ currentAsymmetric = createSM2();
754
+ currentHash = createSM3();
755
+ currentSymmetric = createSM4();
756
+ currentPassword = createPasswordFunctions({ hash: currentHash });
757
+ initialized = true;
758
+ logger.info("Crypto module initialized");
759
+ return ok(void 0);
760
+ } catch (error) {
761
+ currentAsymmetric = null;
762
+ currentHash = null;
763
+ currentSymmetric = null;
764
+ currentPassword = null;
765
+ initialized = false;
766
+ logger.error("Crypto module initialization failed", { error });
767
+ return err(
768
+ HaiCryptoError.INIT_FAILED,
769
+ cryptoM("crypto_initFailed", {
770
+ params: { error: error instanceof Error ? error.message : String(error) }
771
+ }),
772
+ error
773
+ );
774
+ } finally {
775
+ initInProgress = false;
776
+ }
777
+ },
778
+ /** 非对称加密操作(未初始化时所有方法返回 NOT_INITIALIZED) */
779
+ get asymmetric() {
780
+ return currentAsymmetric ?? notInitializedAsymmetric;
781
+ },
782
+ /** 哈希操作(未初始化时所有方法返回 NOT_INITIALIZED) */
783
+ get hash() {
784
+ return currentHash ?? notInitializedHash;
785
+ },
786
+ /** 对称加密操作(未初始化时所有方法返回 NOT_INITIALIZED) */
787
+ get symmetric() {
788
+ return currentSymmetric ?? notInitializedSymmetric;
789
+ },
790
+ /** 密码哈希操作(未初始化时所有方法返回 NOT_INITIALIZED) */
791
+ get password() {
792
+ return currentPassword ?? notInitializedPassword;
793
+ },
794
+ /** 是否已初始化 */
795
+ get isInitialized() {
796
+ return initialized;
797
+ },
798
+ /**
799
+ * 关闭加密模块,释放内部状态
800
+ *
801
+ * 关闭后访问 asymmetric/hash/symmetric/password 会返回 NOT_INITIALIZED 错误。
802
+ */
803
+ async close() {
804
+ if (!initialized) {
805
+ logger.info("Crypto module already closed, skipping");
806
+ return;
807
+ }
808
+ logger.info("Closing crypto module");
809
+ currentAsymmetric = null;
810
+ currentHash = null;
811
+ currentSymmetric = null;
812
+ currentPassword = null;
813
+ initialized = false;
814
+ logger.info("Crypto module closed");
815
+ }
816
+ };
817
+
818
+ export { HaiCryptoError, crypto2 as crypto };
819
+ //# sourceMappingURL=index.js.map
820
+ //# sourceMappingURL=index.js.map