sm-crypto-wasm 0.7.0 → 0.7.1

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.
Files changed (126) hide show
  1. package/README.md +41 -876
  2. package/dist/asn1/decode.d.ts +103 -12
  3. package/dist/asn1/decode.js +129 -10
  4. package/dist/asn1/encode.d.ts +120 -19
  5. package/dist/asn1/encode.js +158 -19
  6. package/dist/asn1/index.d.ts +6 -0
  7. package/dist/asn1/index.js +6 -0
  8. package/dist/asn1/oid.d.ts +18 -0
  9. package/dist/asn1/oid.js +30 -1
  10. package/dist/asn1/time.d.ts +32 -0
  11. package/dist/asn1/time.js +55 -1
  12. package/dist/asn1/types.d.ts +100 -16
  13. package/dist/asn1/types.js +47 -6
  14. package/dist/cms/errors.d.ts +12 -2
  15. package/dist/cms/errors.js +7 -1
  16. package/dist/cms/index.d.ts +5 -0
  17. package/dist/cms/index.js +5 -0
  18. package/dist/cms/oids.d.ts +37 -7
  19. package/dist/cms/oids.js +37 -7
  20. package/dist/cms/parse.d.ts +155 -13
  21. package/dist/cms/parse.js +98 -4
  22. package/dist/cms/sign.d.ts +80 -10
  23. package/dist/cms/sign.js +54 -2
  24. package/dist/cms/verify.d.ts +84 -9
  25. package/dist/cms/verify.js +54 -5
  26. package/dist/common/hybrid.d.ts +25 -0
  27. package/dist/common/hybrid.js +25 -0
  28. package/dist/common/sealed.d.ts +119 -15
  29. package/dist/common/sealed.js +67 -9
  30. package/dist/esbuild-plugin.d.ts +74 -5
  31. package/dist/esbuild-plugin.js +69 -4
  32. package/dist/index.d.ts +19 -0
  33. package/dist/index.js +22 -0
  34. package/dist/pem/asn1.d.ts +13 -0
  35. package/dist/pem/asn1.js +13 -0
  36. package/dist/pem/index.d.ts +2 -0
  37. package/dist/pem/index.js +2 -0
  38. package/dist/pem/pkcs8.d.ts +64 -8
  39. package/dist/pem/pkcs8.js +66 -6
  40. package/dist/pem/sm2.d.ts +76 -4
  41. package/dist/pem/sm2.js +169 -12
  42. package/dist/pem/sm9.d.ts +42 -2
  43. package/dist/pem/sm9.js +42 -2
  44. package/dist/runtime/base64.core.d.ts +25 -1
  45. package/dist/runtime/base64.core.js +31 -1
  46. package/dist/runtime/convert.d.ts +70 -7
  47. package/dist/runtime/convert.js +129 -9
  48. package/dist/runtime/errors.d.ts +64 -8
  49. package/dist/runtime/errors.js +64 -8
  50. package/dist/runtime/index.d.ts +5 -0
  51. package/dist/runtime/index.js +5 -0
  52. package/dist/runtime/loadWasm.browser.d.ts +33 -0
  53. package/dist/runtime/loadWasm.browser.js +46 -0
  54. package/dist/runtime/loadWasm.core.d.ts +59 -3
  55. package/dist/runtime/loadWasm.core.js +53 -3
  56. package/dist/runtime/loadWasm.d.ts +44 -0
  57. package/dist/runtime/loadWasm.js +79 -4
  58. package/dist/runtime/random.browser.d.ts +24 -0
  59. package/dist/runtime/random.browser.js +31 -1
  60. package/dist/runtime/random.core.d.ts +21 -0
  61. package/dist/runtime/random.core.js +27 -0
  62. package/dist/runtime/random.d.ts +28 -0
  63. package/dist/runtime/random.js +40 -1
  64. package/dist/sm2/exchange.d.ts +132 -16
  65. package/dist/sm2/exchange.js +120 -9
  66. package/dist/sm2/hybrid.d.ts +65 -3
  67. package/dist/sm2/hybrid.js +53 -1
  68. package/dist/sm2/index.d.ts +260 -21
  69. package/dist/sm2/index.js +346 -23
  70. package/dist/sm2/kem.d.ts +62 -5
  71. package/dist/sm2/kem.js +37 -2
  72. package/dist/sm2/keyInput.d.ts +36 -3
  73. package/dist/sm2/keyInput.js +36 -3
  74. package/dist/sm3/index.d.ts +30 -0
  75. package/dist/sm3/index.js +55 -1
  76. package/dist/sm4/index.d.ts +125 -17
  77. package/dist/sm4/index.js +201 -14
  78. package/dist/sm9/core.d.ts +58 -8
  79. package/dist/sm9/core.js +36 -5
  80. package/dist/sm9/hybrid.d.ts +66 -3
  81. package/dist/sm9/hybrid.js +46 -1
  82. package/dist/sm9/index.d.ts +265 -19
  83. package/dist/sm9/index.js +175 -5
  84. package/dist/sm9/input.d.ts +37 -2
  85. package/dist/sm9/input.js +50 -1
  86. package/dist/sm9/kem.d.ts +57 -4
  87. package/dist/sm9/kem.js +47 -1
  88. package/dist/webcrypto/algorithms.d.ts +58 -12
  89. package/dist/webcrypto/algorithms.js +58 -10
  90. package/dist/webcrypto/cryptoKey.d.ts +81 -15
  91. package/dist/webcrypto/cryptoKey.js +69 -11
  92. package/dist/webcrypto/errors.d.ts +29 -5
  93. package/dist/webcrypto/errors.js +29 -5
  94. package/dist/webcrypto/index.d.ts +10 -0
  95. package/dist/webcrypto/index.js +10 -0
  96. package/dist/webcrypto/subtle.d.ts +53 -3
  97. package/dist/webcrypto/subtle.js +69 -1
  98. package/dist/x509/encode.d.ts +86 -16
  99. package/dist/x509/encode.js +89 -16
  100. package/dist/x509/errors.d.ts +16 -1
  101. package/dist/x509/errors.js +16 -1
  102. package/dist/x509/extensions.d.ts +46 -7
  103. package/dist/x509/extensions.js +67 -9
  104. package/dist/x509/index.d.ts +7 -0
  105. package/dist/x509/index.js +19 -0
  106. package/dist/x509/ip.d.ts +9 -0
  107. package/dist/x509/ip.js +21 -0
  108. package/dist/x509/issue.d.ts +45 -2
  109. package/dist/x509/issue.js +107 -0
  110. package/dist/x509/oids.d.ts +41 -7
  111. package/dist/x509/oids.js +58 -7
  112. package/dist/x509/parse.d.ts +21 -0
  113. package/dist/x509/parse.js +115 -1
  114. package/dist/x509/sha1.d.ts +12 -1
  115. package/dist/x509/sha1.js +18 -1
  116. package/dist/x509/sha256.d.ts +12 -1
  117. package/dist/x509/sha256.js +31 -1
  118. package/dist/x509/sig.d.ts +15 -0
  119. package/dist/x509/sig.js +28 -1
  120. package/dist/x509/types.d.ts +312 -57
  121. package/dist/x509/types.js +6 -0
  122. package/dist/x509/verify.d.ts +51 -4
  123. package/dist/x509/verify.js +42 -3
  124. package/dist/zuc/index.d.ts +128 -10
  125. package/dist/zuc/index.js +136 -7
  126. package/package.json +53 -36
package/README.md CHANGED
@@ -1,54 +1,28 @@
1
1
  # sm-crypto-wasm
2
2
 
3
- [🇨🇳 中文](#zh-阅读下面的中文文档) · [🇺🇸 English](#en-english-documentation) · [🇷🇺 Русский](#ru-документация-на-русском)
3
+ [![npm version](https://img.shields.io/npm/v/sm-crypto-wasm.svg?style=flat-square)](https://www.npmjs.com/package/sm-crypto-wasm)
4
+ [![npm downloads](https://img.shields.io/npm/dm/sm-crypto-wasm.svg?style=flat-square)](https://www.npmjs.com/package/sm-crypto-wasm)
5
+ [![license](https://img.shields.io/npm/l/sm-crypto-wasm.svg?style=flat-square)](https://github.com/jktantan/sm-crypto-wasm/blob/main/LICENSE)
6
+ [![Node.js](https://img.shields.io/node/v/sm-crypto-wasm.svg?style=flat-square)](https://nodejs.org/)
4
7
 
5
- Chinese cryptography standards (**SM2 / SM3 / SM4 / SM9 / ZUC**) implemented in
6
- **AssemblyScript → WebAssembly**. Works on Node 18+ and modern browsers (incl.
7
- Web Workers). Algorithms are split into sub-packages and lazy-loaded on demand.
8
+ **简体中文** · [English](https://github.com/jktantan/sm-crypto-wasm/blob/main/docs/README.en.md) · [Русский](https://github.com/jktantan/sm-crypto-wasm/blob/main/docs/README.ru.md) · [完整文档](https://jktantan.github.io/sm-crypto-wasm/)
8
9
 
9
- - **Performance**: elliptic curves and bilinear pairings run in WASM, ~10×
10
- faster than pure-JS implementations;
11
- - **Zero toolchain**: the published artifact is `.wasm` + a thin TS shim; users
12
- do not need `node-gyp`, Rust, or a compiler;
13
- - **Runtime-agnostic**: the same API works in Node, browsers, and Workers;
14
- - **Standards-aligned**: implementations follow the GM/T 0003 / 0004 / 0002 /
15
- 0044 / 0001 series, cross-validated against official test vectors and
16
- OpenSSL / GmSSL / gmkitx.
17
-
18
- ---
19
-
20
- ## zh 阅读下面的中文文档
21
-
22
- # sm-crypto-wasm
23
-
24
- > 📖 **完整文档**(每个算法的参数表、用法与**可交互 Playground**):<https://jktantan.github.io/sm-crypto-wasm/>
10
+ > Chinese cryptography standards (**SM2 / SM3 / SM4 / SM9 / ZUC**) implemented in **AssemblyScript → WebAssembly**.
25
11
 
26
12
  国密算法(SM2 / SM3 / SM4 / SM9 / ZUC)的 **AssemblyScript → WebAssembly** 实现,Node 18+ 与现代浏览器(含 Web Worker)均可使用,按算法分包、按需懒加载。
27
13
 
28
- - **性能**:热点(大整数模运算 / hash 压缩 / 分组密码轮函数)跑在 WASM 里。轻量算法
29
- (SM3 / SM4 / ZUC)实测快于纯 JS 参考实现 1.1~3.0×;椭圆曲线部分(SM2)仍在优化中,
30
- 见下方 **性能** 小节;
14
+ - **性能**:热点(大整数模运算 / hash 压缩 / 分组密码轮函数)跑在 WASM 里。与同机纯 JS 参考实现相比,SM3 / ZUC 快 2.3\~3.5×、SM4 六种模式快 3.6\~11.2×、SM2 快 1.8\~7.6×;
31
15
  - **零工具链**:发布物是 `.wasm` + 薄 TS 胶水,使用者不需要 `node-gyp` / Rust / 编译器;
32
16
  - **跨运行时**:同一份 API 在 Node、浏览器、Worker 下一致;
33
17
  - **国标对齐**:实现按 GM/T 0003 / 0004 / 0002 / 0044 / 0001 系列,测试用官方向量与 OpenSSL / GmSSL / gmkitx 交叉对拍。
34
18
 
35
19
  ---
36
20
 
37
- ## 性能
38
-
39
- 热点(大整数模运算、hash 压缩函数、分组密码轮函数、双线性对)在 WASM 内执行。
40
- 所有经过对比的公开操作都快于同机纯 JS 参考实现:SM3 / ZUC 快 1.8\~2.3×,
41
- SM4 六种模式 3.6\~11.2×,SM2 1.8\~7.6×。
42
-
43
- 完整的逐项操作表、与 `gmkitx` 的对比,以及「为什么是这个形状」的分析,见
44
- **[性能页](https://jktantan.github.io/sm-crypto-wasm/performance)**。本机复现:
45
- `pnpm bench`。
46
-
47
21
  ## 安装
48
22
 
49
23
  ```bash
50
- pnpm add sm-crypto-wasm
51
- # 或 npm i sm-crypto-wasm / yarn add sm-crypto-wasm
24
+ npm install sm-crypto-wasm
25
+ # 或 pnpm add sm-crypto-wasm / yarn add sm-crypto-wasm
52
26
  ```
53
27
 
54
28
  要求 Node **>= 18**(浏览器需支持 WebAssembly 与 `crypto.getRandomValues`)。
@@ -225,16 +199,18 @@ X.509 / CMS 不做证书链路径校验、CRL/OCSP;WebCrypto 不是原生 `cry
225
199
 
226
200
  ## API 一览
227
201
 
202
+ 下表列出主要 API;所有模块都导出 `ensureLoaded()`,完整导出以 `dist/index.d.ts` 或对应子路径的 `.d.ts` 为准。
203
+
228
204
  | 模块 | 函数 |
229
205
  |---|---|
230
- | `sm2` | `generateKeyPair`(async) · `getPublicKey` · `sign` · `verify` · `encrypt` · `decrypt` · `preparePublicKey`(async) · `configurePreparedKeyCache` · `clearPreparedPublicKeys` · `getPreparedPublicKeys` · `kemEncrypt` · `kemDecrypt` · `hybridEncrypt` · `hybridDecrypt` · `sealedToObject` · `sealedFromObject` · `createExchangeInitiator`(async) · `createExchangeResponder`(async) |
206
+ | `sm2` | `generateKeyPair`(async) · `getPublicKey` · `sign` · `signWithID` · `verify` · `verifyWithID` · `encrypt` · `decrypt` · `configurePublicKeyCache` · `clearPublicKeyCache` · `preparePublicKey`(async) · `configurePreparedKeyCache` · `clearPreparedPublicKey` · `getPreparedPublicKey` · `clearPreparedPublicKeys` · `getPreparedPublicKeys` · `createSignaturePointPool`(async) · `kemEncrypt` · `kemDecrypt` · `hybridEncrypt` · `hybridDecrypt` · `sealedToObject` · `sealedFromObject` · `createExchangeInitiator`(async) · `createExchangeResponder`(async) |
231
207
  | `webcrypto` | `subtle` · `CryptoKey` |
232
208
  | `cms` | `signData` · `verifySignedData` · `parseSignedData` · `CmsError` |
233
209
  | `x509` | `parseCertificate` · `parseCertificates` · `createSelfSignedCertificate` · `issueCertificate` · `parseDistinguishedName` · `verifyCertificateSignature` |
234
210
  | `sm3` | `hash` · `hmac` |
235
211
  | `sm4` | `encryptECB`/`decryptECB` · `encryptCBC`/`decryptCBC` · `encryptCTR`/`decryptCTR` · `encryptCFB`/`decryptCFB` · `encryptOFB`/`decryptOFB` · `encryptGCM`/`decryptGCM` |
236
212
  | `sm9` | `generateKeyPair`(async) · `sign` · `verify` · `encrypt` · `decrypt` · `kemEncrypt` · `kemDecrypt` · `hybridEncrypt` · `hybridDecrypt` · `sealedToObject` · `sealedFromObject` · `getSignPublicKey` · `getEncPublicKey` · `getUserPublicKey` · `createExchangeInitiator`(async) · `createExchangeResponder`(async) |
237
- | `zuc` | `zucKeystream` · `zucEncrypt` · `zucDecrypt` · `zucMac` · `zucEia3Iv` · `configureReuseGuard` |
213
+ | `zuc` | `zucKeystream` · `zucEncrypt` · `zucDecrypt` · `zucMac` · `zucEia3Iv` · `configureReuseGuard` · `resetReuseGuard` |
238
214
 
239
215
  文档名别名(与主名指向同一实现):`sm4.encryptCTR`/`decryptCTR` 等价 `cryptCTR`、`encryptOFB`/`decryptOFB` 等价 `cryptOFB`;`zuc.encrypt`/`decrypt`/`generateKeystream`/`mac`/`eia3` 等价 `zucEncrypt`/`zucDecrypt`/`zucKeystream`/`zucMac`。
240
216
 
@@ -249,7 +225,7 @@ import { hash } from 'sm-crypto-wasm/sm3';
249
225
  import { encryptCBC } from 'sm-crypto-wasm/sm4';
250
226
  ```
251
227
 
252
- 可用子路径:`sm-crypto-wasm/sm2` · `/sm3` · `/sm4` · `/sm9` · `/x509` · `/cms` · `/webcrypto` · `/zuc`。
228
+ 可用子路径:`sm-crypto-wasm/sm2` · `/sm3` · `/sm4` · `/sm9` · `/x509` · `/cms` · `/webcrypto` · `/zuc`;另有 `sm-crypto-wasm/esbuild-plugin`(见下方打包器支持)。
253
229
 
254
230
  ---
255
231
 
@@ -339,14 +315,26 @@ try {
339
315
 
340
316
  ---
341
317
 
318
+ ## 性能
319
+
320
+ 热点(大整数模运算、hash 压缩函数、分组密码轮函数、双线性对)在 WASM 内执行。
321
+ 所有经过对比的公开操作都快于同机纯 JS 参考实现:SM3 hash/hmac 快 3.1\~3.5×、ZUC EEA3 快 2.3×,
322
+ SM4 六种模式 3.6\~11.2×,SM2 1.8\~7.6×。
323
+
324
+ 完整的逐项操作表、与 `gmkitx` 的对比,以及「为什么是这个形状」的分析,见
325
+ **[性能页](https://jktantan.github.io/sm-crypto-wasm/performance)**。本机复现:
326
+ `pnpm bench`。
327
+
328
+ ---
329
+
342
330
  ## 开发
343
331
 
344
332
  ```bash
345
333
  pnpm install
346
334
  pnpm build # clean + asc(5 个 .wasm)+ tsc → dist/(无 source map)
347
335
  pnpm build:ts:debug # 需要调试时:带 .js.map / .d.ts.map 重编译 TS 到 dist/
348
- pnpm test # Node runner(vitest)
349
- pnpm test:browser # 浏览器 runner(happy-dom)
336
+ pnpm test # 构建 wasm + Node 测试(vitest)
337
+ pnpm test:browser # 构建 wasm + 浏览器 runner(happy-dom)
350
338
  pnpm bench # mitata 微型基准(5 大块算法)
351
339
  pnpm lint # biome(lint + format)
352
340
  pnpm typecheck # tsc --noEmit
@@ -372,851 +360,28 @@ pnpm typecheck # tsc --noEmit
372
360
 
373
361
  ---
374
362
 
375
- ## License
376
-
377
- [Apache-2.0](./LICENSE)。
378
-
379
- 本项目的算法实现参考/移植了以下同样以 Apache-2.0 发布的项目,归属声明见 [`NOTICE`](./NOTICE):
380
-
381
- - [GmSSL](https://github.com/guanzhi/GmSSL) —— SM2 / SM9 的算法语义与命名对齐基准;
382
- - [gmkit / gmkitx](https://github.com/gmkits/gmkit) —— ZUC 实现的移植来源。
383
-
384
- ---
385
-
386
- <details>
387
- <summary><h2 id="en-english-documentation" style="display:inline-block">🇺🇸 English</h2></summary>
388
-
389
- # sm-crypto-wasm
390
-
391
- > 📖 **Full documentation** (per-algorithm parameter tables, usage and **interactive Playgrounds**): <https://jktantan.github.io/sm-crypto-wasm/en/>
392
-
393
- An **AssemblyScript → WebAssembly** implementation of Chinese cryptography
394
- standards (**SM2 / SM3 / SM4 / SM9 / ZUC**). Runs on Node 18+ and modern
395
- browsers (including Web Workers). Algorithms are split into sub-packages and
396
- lazy-loaded on demand.
397
-
398
- - **Performance**: hot paths (bignum modular arithmetic, hash compression,
399
- block-cipher rounds) run inside WASM. The lightweight algorithms (SM3 / SM4 /
400
- ZUC) measure 1.1~3.0× faster than the pure-JS reference implementation; the
401
- elliptic-curve side (SM2) is still being optimised — see **Performance** below;
402
- - **Zero toolchain**: the published artifact is `.wasm` plus a thin TS shim;
403
- users don't need `node-gyp`, Rust, or a compiler;
404
- - **Runtime-agnostic**: the same API works in Node, browsers, and Workers;
405
- - **Standards-aligned**: implementations follow the GM/T 0003 / 0004 / 0002 /
406
- 0044 / 0001 series and are cross-checked against official vectors using
407
- OpenSSL / GmSSL / gmkitx.
408
-
409
- ---
410
-
411
- ## Performance
412
-
413
- Hot paths (bignum modular arithmetic, hash compression, block-cipher rounds,
414
- pairings) run inside WASM. **Every operation with a pure-JS counterpart is faster
415
- than it**: SM3 / ZUC by 1.8–2.3×, the six SM4 modes by 3.6–11.2×, SM2 by 1.8–7.6×.
416
-
417
- The full per-operation table, the head-to-head against `gmkitx`, and the
418
- "why it looks like this" notes live on the
419
- **[performance page](https://jktantan.github.io/sm-crypto-wasm/en/performance)**.
420
- Reproduce locally with `pnpm bench`.
421
-
422
- ## Installation
423
-
424
- ```bash
425
- pnpm add sm-crypto-wasm
426
- # or: npm i sm-crypto-wasm / yarn add sm-crypto-wasm
427
- ```
428
-
429
- Requires Node **>= 18** (browsers must support WebAssembly and
430
- `crypto.getRandomValues`).
431
-
432
- ---
433
-
434
- ## Quick Start
435
-
436
- > **Call `await algo.ensureLoaded()` once before the first use of each
437
- > algorithm.** Instantiating a WASM module is asynchronous; afterwards every
438
- > business function is synchronous. Only the `.wasm` files you actually use
439
- > are downloaded.
440
-
441
- ### SM3 — hash & HMAC
442
-
443
- ```ts
444
- import { sm3 } from 'sm-crypto-wasm';
445
-
446
- await sm3.ensureLoaded();
447
- sm3.hash('616263'); // '66c7f0f4…8f4ba8e0' — hex input is decoded byte-wise
448
- sm3.hash(new Uint8Array([0x61, 0x62, 0x63])); // same output; byte input is the most ergonomic
449
- sm3.hmac('00112233445566778899aabbccddeeff', '616263');
450
- ```
451
-
452
- ### SM4 — symmetric encryption (ECB / CBC / CTR / CFB / OFB / GCM)
453
-
454
- ```ts
455
- import { sm4 } from 'sm-crypto-wasm';
456
-
457
- await sm4.ensureLoaded();
458
- const key = '0123456789abcdeffedcba9876543210';
459
- const iv = '000102030405060708090a0b0c0d0e0f';
460
- const msg16 = '00112233445566778899aabbccddeeff'; // 16 bytes
461
- const aad = 'aabbccdd'; // additional authenticated data (hex)
462
-
463
- sm4.encryptECB(key, msg16); // ECB has no padding; input must be a multiple of 16 bytes
464
- sm4.encryptCBC(key, iv, '616263'); // CBC uses PKCS#7 padding; any length is fine
465
- sm4.decryptCBC(key, iv, sm4.encryptCBC(key, iv, '616263'));
466
- sm4.encryptCTR(key, iv, '616263'); // CTR / OFB are stream modes (aliases of cryptCTR / cryptOFB)
467
- const { ct, tag } = sm4.encryptGCM(key, iv, '616263', aad);
468
- sm4.decryptGCM(key, iv, ct, tag, aad); // throws DecryptionError on auth failure; pass { safeMode: true } to suppress
469
- ```
470
-
471
- ### SM2 — key generation / signature / verification / encryption
472
-
473
- ```ts
474
- import { sm2 } from 'sm-crypto-wasm';
475
-
476
- const { privateKeyPem, publicKeyPem } = await sm2.generateKeyPair(); // async; returns PKCS#8 / SPKI PEM
477
-
478
- const sig = sm2.sign(privateKeyPem, '616263'); // default SM3withSM2; output is r||s as hex
479
- sm2.verify(publicKeyPem, '616263', sig); // → true
480
-
481
- const ct = sm2.encrypt(publicKeyPem, '616263'); // default C1C3C2
482
- const ct2 = sm2.encrypt(publicKeyPem, '616263', { outputFormat: 'C1C2C3' });
483
- sm2.decrypt(privateKeyPem, ct); // decrypt side auto-detects C1C3C2 / C1C2C3
484
- sm2.decrypt(privateKeyPem, ct, { safeMode: true }); // returns null instead of throwing on failure
485
- ```
486
-
487
- Keys may also be passed as raw hex / `Uint8Array` (32-byte private key, 64- or
488
- 65-byte public key).
489
-
490
- ### SM2 — key encapsulation & hybrid encryption
491
-
492
- ```ts
493
- import { sm2 } from 'sm-crypto-wasm';
494
-
495
- await sm2.ensureLoaded();
496
-
497
- // KEM: protect a random symmetric key with the public key (16 or 32 bytes).
498
- const { key, encapsulation } = sm2.kemEncrypt(publicKeyPem, 32);
499
- sm2.kemDecrypt(privateKeyPem, encapsulation, 32); // === key
500
-
501
- // Hybrid encryption: one call performs KEM + SM4-GCM; suited to large messages.
502
- // Returns a versioned sealed value: version || klen || encapLen || encapsulation || iv || tag || ct
503
- const sealed = sm2.hybridEncrypt(publicKeyPem, '616263');
504
- sm2.hybridDecrypt(privateKeyPem, sealed); // → '616263' (plaintext hex)
505
-
506
- // The sealed value can also be inspected as a self-describing object.
507
- const obj = sm2.sealedToObject(sealed);
508
- sm2.hybridDecrypt(privateKeyPem, sm2.sealedFromObject(obj));
509
- ```
510
-
511
- - Every `hybridEncrypt` call generates a fresh 16-byte DEK and 12-byte IV; message strings are interpreted as **hex** (pass `new Uint8Array(0)` for an empty message).
512
- - Hybrid uses both `sm2.wasm` and `sm4.wasm`; warm them up with `await Promise.all([sm2.ensureLoaded(), sm4.ensureLoaded()])` before the first call.
513
- - `hybridDecrypt` supports `{ safeMode: true }`, returning `null` for ciphertext-side failures.
514
- - SM2 has no standardized KEM construction; `kemEncrypt` is the conventional "encrypt a random key with SM2" idiom.
515
-
516
- ### SM2 — key exchange (GM/T 0003.3)
517
-
518
- ```ts
519
- import { sm2 } from 'sm-crypto-wasm';
520
-
521
- const A = await sm2.generateKeyPair();
522
- const B = await sm2.generateKeyPair();
523
-
524
- const alice = await sm2.createExchangeInitiator(A.privateKeyPem, B.publicKeyPem, {
525
- idSelf: 'Alice', idPeer: 'Bob',
526
- });
527
- const bob = await sm2.createExchangeResponder(B.privateKeyPem, A.publicKeyPem, {
528
- idSelf: 'Bob', idPeer: 'Alice',
529
- });
530
-
531
- const r1 = alice.initiate(); // initiator → responder, round 1
532
- const r2 = bob.respond(r1); // responder → initiator, reply
533
- const r3 = alice.confirm(r2); // initiator confirmation (carries S_A for the responder to verify)
534
-
535
- alice.getSharedKey(r3); // both sides derive the same shared secret
536
- bob.getSharedKey(r3);
537
- ```
538
-
539
- ### SM9 — sign / encrypt / KEM (GM/T 0044)
540
-
541
- ```ts
542
- import { sm9 } from 'sm-crypto-wasm';
543
-
544
- // SM9 has two master keys (distinguished by purpose): the sign master uses a
545
- // G2 master public key, the encrypt master uses a G1 master public key.
546
- const signKp = await sm9.generateKeyPair({ id: 'alice@foo.com' }); // default purpose: 'sign'
547
- const encKp = await sm9.generateKeyPair({ id: 'alice@foo.com', purpose: 'enc' });
548
-
549
- const sig = sm9.sign(signKp.masterPrivatePem, 'alice@foo.com', '616263');
550
- sm9.verify(signKp.masterPublicPem, 'alice@foo.com', '616263', sig);
551
- // The first argument of verify / encrypt also accepts the master private key (legacy shape).
552
-
553
- const ct = sm9.encrypt(encKp.masterPublicPem, 'alice@foo.com', '616263');
554
- sm9.decrypt(encKp.masterPrivatePem, 'alice@foo.com', ct); // → '616263'
555
-
556
- // Key encapsulation (KEM): negotiate a symmetric key without sending a message.
557
- // Note the first parameter is the *encryption master private key* (KEM needs it to
558
- // derive Ppub-e; encrypt() above can take a master public key instead).
559
- const { key, encapsulation } = sm9.kemEncrypt(encKp.masterPrivatePem, 'alice@foo.com', 32);
560
- sm9.kemDecrypt(encKp.masterPrivatePem, 'alice@foo.com', encapsulation, 32); // === key
561
-
562
- // Hybrid encryption: encapsulate a 16-byte DEK with the master public (or private) key + id,
563
- // then encrypt the message with SM4-GCM.
564
- const sealed = sm9.hybridEncrypt(encKp.masterPublicPem, 'alice@foo.com', '616263');
565
- sm9.hybridDecrypt(encKp.masterPrivatePem, 'alice@foo.com', sealed); // → '616263'
566
- ```
567
-
568
- > SM9 hybrid uses both `sm9.wasm` and `sm4.wasm`: call `await sm9.ensureLoaded()` and `await sm4.ensureLoaded()` before the first use.
569
-
570
- ### ZUC — EEA3 confidentiality + EIA3 integrity
571
-
572
- ```ts
573
- import { zuc } from 'sm-crypto-wasm';
574
-
575
- await zuc.ensureLoaded();
576
- const key = '000102030405060708090a0b0c0d0e0f';
577
- const iv = '00000000000000000000000000000000';
578
-
579
- zuc.zucKeystream(key, iv, 16); // keystream (Uint8Array)
580
- zuc.zucEncrypt(key, iv, '616263'); // EEA3 — each (key, iv, counter) may be encrypted once
581
- zuc.zucDecrypt(key, iv, ct); // decryption (exempt from the reuse guard)
582
-
583
- // EIA3 integrity tag (32-bit); IV is assembled from COUNT / BEARER / DIRECTION
584
- const macIv = zuc.zucEia3Iv(0x66035492, 0x0f, 0);
585
- zuc.zucMac(key, macIv, '616263'); // → 4-byte tag
586
- ```
587
-
588
- ---
589
-
590
- ### X.509 · CMS · WebCrypto
591
-
592
- ```ts
593
- import { x509, cms, webcrypto } from 'sm-crypto-wasm';
594
-
595
- const cert = x509.parseCertificate(pem);
596
- const signed = cms.signData({ data, privateKey, certificate: cert });
597
- const digest = await webcrypto.subtle.digest('SM3', data);
598
- ```
599
-
600
- - **X.509** — parsing, single-certificate verification, self-signed / simple CA issuing ([docs](https://jktantan.github.io/sm-crypto-wasm/en/guide/x509));
601
- - **CMS** — detached / attached SignedData, original-DER `signedAttrs` verification, BER ([docs](https://jktantan.github.io/sm-crypto-wasm/en/guide/cms));
602
- - **WebCrypto** — a `subtle`-style subset: SM3 / HMAC(SM3) / SM2 / SM4 ([docs](https://jktantan.github.io/sm-crypto-wasm/en/guide/webcrypto)).
603
-
604
- No certificate-chain validation or CRL/OCSP for X.509/CMS; the WebCrypto adapter is not a polyfill of native `crypto.subtle`.
605
-
606
- ---
607
-
608
- ## API at a glance
609
-
610
- | Module | Functions |
611
- |---|---|
612
- | `sm2` | `generateKeyPair` (async) · `getPublicKey` · `sign` · `verify` · `encrypt` · `decrypt` · `preparePublicKey` (async) · `configurePreparedKeyCache` · `clearPreparedPublicKeys` · `getPreparedPublicKeys` · `kemEncrypt` · `kemDecrypt` · `hybridEncrypt` · `hybridDecrypt` · `sealedToObject` · `sealedFromObject` · `createExchangeInitiator` (async) · `createExchangeResponder` (async) |
613
- | `webcrypto` | `subtle` · `CryptoKey` |
614
- | `cms` | `signData` · `verifySignedData` · `parseSignedData` · `CmsError` |
615
- | `x509` | `parseCertificate` · `parseCertificates` · `createSelfSignedCertificate` · `issueCertificate` · `parseDistinguishedName` · `verifyCertificateSignature` |
616
- | `sm3` | `hash` · `hmac` |
617
- | `sm4` | `encryptECB` / `decryptECB` · `encryptCBC` / `decryptCBC` · `encryptCTR` / `decryptCTR` · `encryptCFB` / `decryptCFB` · `encryptOFB` / `decryptOFB` · `encryptGCM` / `decryptGCM` |
618
- | `sm9` | `generateKeyPair` (async) · `sign` · `verify` · `encrypt` · `decrypt` · `kemEncrypt` · `kemDecrypt` · `hybridEncrypt` · `hybridDecrypt` · `sealedToObject` · `sealedFromObject` · `getSignPublicKey` · `getEncPublicKey` · `getUserPublicKey` · `createExchangeInitiator` (async) · `createExchangeResponder` (async) |
619
- | `zuc` | `zucKeystream` · `zucEncrypt` · `zucDecrypt` · `zucMac` · `zucEia3Iv` · `configureReuseGuard` |
620
-
621
- Documented aliases (they point to the same implementation):
622
- `sm4.encryptCTR` / `decryptCTR` alias `cryptCTR`,
623
- `encryptOFB` / `decryptOFB` alias `cryptOFB`;
624
- `zuc.encrypt` / `decrypt` / `generateKeystream` / `mac` / `eia3` alias
625
- `zucEncrypt` / `zucDecrypt` / `zucKeystream` / `zucMac`.
626
-
627
- ---
628
-
629
- ## Using a single algorithm (sub-path imports)
630
-
631
- For size-sensitive scenarios, you can skip the aggregated entry point and
632
- load just the `.wasm` you need:
633
-
634
- ```ts
635
- import { hash } from 'sm-crypto-wasm/sm3';
636
- import { encryptCBC } from 'sm-crypto-wasm/sm4';
637
- ```
638
-
639
- Available sub-paths: `sm-crypto-wasm/sm2` · `/sm3` · `/sm4` · `/sm9` · `/x509` · `/cms` · `/webcrypto` · `/zuc`.
640
-
641
- ---
642
-
643
- ## Cross-runtime
644
-
645
- | Environment | Loading path |
646
- |---|---|
647
- | Node 18+ | reads `dist/<algo>/<algo>.wasm` via `fs/promises`; `loadWasmSync()` available |
648
- | Browser (via a bundler) | `fetch(new URL('../<algo>/<algo>.wasm', import.meta.url))`; the bundler archives and rewrites it |
649
- | Web Worker | the loader is DOM-free and works inside a Worker |
650
- | Deno / Bun | follows the Node path |
651
- | WeChat built-in browser | same as browsers (`fetch` + `crypto.getRandomValues`); see [WeChat & Mini Programs](https://jktantan.github.io/sm-crypto-wasm/en/guide/wechat) |
652
- | WeChat Mini Program | `WXWebAssembly` + `wx.getRandomValues`; see [WeChat & Mini Programs](https://jktantan.github.io/sm-crypto-wasm/en/guide/wechat) |
653
-
654
- ### Bundler support
655
-
656
- The package ships two implementations (a Node one reading files, a browser one
657
- using `fetch`) and `package.json`'s `browser` field switches between them at
658
- build time. So **`node:*` never appears in a browser bundle** — bundlers that do
659
- not externalise node built-ins (Webpack, esbuild, Rspack) no longer fail at
660
- resolve time with `Can't resolve 'node:fs'`.
661
-
662
- | Bundler | Configuration | wasm asset |
663
- |---|---|---|
664
- | **Vite 5** | none | archived into the output (`assets/*.wasm`) |
665
- | **Webpack 5** | none | emitted as an asset |
666
- | **Rspack 2** | none | emitted as an asset (hashed filenames) |
667
- | **Turbopack** (Next.js 16 default) | none | archived under `static/media/*.wasm` |
668
- | **esbuild** | one plugin line (below) | esbuild does not handle files pointed at by `new URL(..., import.meta.url)` |
669
- | **Node 18+** | none | reads `dist/<algo>/<algo>.wasm` |
670
-
671
- esbuild users add a small bundled plugin that drops the wasm where the runtime looks for it:
672
-
673
- ```ts
674
- import { smCryptoWasm } from 'sm-crypto-wasm/esbuild-plugin';
675
-
676
- await esbuild.build({
677
- entryPoints: ['src/main.ts'],
678
- bundle: true,
679
- platform: 'browser',
680
- format: 'esm',
681
- outdir: 'dist/assets', // output JS lands here
682
- plugins: [smCryptoWasm()], // → wasm copied to dist/<algo>/<algo>.wasm
683
- });
684
- ```
685
-
686
- Prefer no plugin? Copy `node_modules/sm-crypto-wasm/dist/{sm2,sm3,sm4,sm9,zuc}`
687
- into the **parent** directory of the output JS (i.e. `dist/` in the example above).
688
-
689
- Browsers need a **secure context** (`https://` or `localhost`) for
690
- `crypto.getRandomValues`; when it is missing the library **throws** (fail-closed)
691
- rather than falling back to a non-cryptographic source.
692
-
693
- Strict-streaming WASM environments such as Cloudflare Workers / Vercel Edge are
694
- **out of scope**; so is loading the raw ESM output directly via
695
- `<script type="module">` (without a bundler).
696
-
697
- ---
698
-
699
- ## Error handling
700
-
701
- All "rejected because of input" errors are subclasses of `SmCryptoError`, in
702
- two branches:
703
-
704
- ```ts
705
- import { SmCryptoError, InvalidKeyError, InvalidPublicKeyError,
706
- CiphertextError, InvalidCiphertextError, DecryptionError } from 'sm-crypto-wasm';
707
-
708
- try {
709
- sm2.decrypt(priv, ct);
710
- } catch (e) {
711
- if (e instanceof DecryptionError) { /* ciphertext / key mismatch */ }
712
- else if (e instanceof CiphertextError) { /* structure or validation issue */ }
713
- else if (e instanceof InvalidKeyError) { /* key format / length / curve */ }
714
- else throw e;
715
- }
716
- ```
717
-
718
- - Key branch: `InvalidKeyError` (with `InvalidPublicKeyError` as a subclass);
719
- - Ciphertext branch: `CiphertextError` (with `InvalidCiphertextError` for
720
- structural errors and `DecryptionError` for validation failures);
721
- - Type or range problems (e.g. length caps, invalid enum values) keep the
722
- host's native `TypeError` / `RangeError`.
723
-
724
- ---
725
-
726
- ## Security notes
727
-
728
- - **The library uses only the host's RNG**: `crypto.getRandomValues`
729
- (browsers / Node 19+) or the Node 18 `node:crypto` shim. **No RNG is
730
- implemented inside the library.**
731
- - **No disk writes, no logs of private keys**: the library never serializes
732
- keys to disk or to logs.
733
- - **ZUC reuse guard**: encrypting twice with the same `(key, iv, counter)`
734
- throws (keystream reuse is a confidentiality breach). If you manage the
735
- counter yourself, you can disable the guard globally with
736
- `configureReuseGuard({ enabled: false })` or per call via `{ reuseGuard: false }`.
737
- - **SM2 public-key validity**: `encrypt` throws `InvalidPublicKeyError` for
738
- points not on the curve; `verify` simply returns `false`.
739
- - No integration with crypto hardware / HSM / PKCS#11 / soft-cert handles;
740
- no key parsing outside PEM.
363
+ ## 贡献
741
364
 
742
- ---
365
+ 欢迎提交 Issue 与 Pull Request。开发环境、测试命令与 PR 要求见
366
+ [CONTRIBUTING.md](https://github.com/jktantan/sm-crypto-wasm/blob/main/CONTRIBUTING.md);
367
+ agent 协作、中英双语注释与代码规范见
368
+ [AGENTS.md](https://github.com/jktantan/sm-crypto-wasm/blob/main/AGENTS.md);
369
+ 安全问题请按
370
+ [SECURITY.md](https://github.com/jktantan/sm-crypto-wasm/blob/main/SECURITY.md) 报告。
743
371
 
744
- ## Development
372
+ 提交前请运行:
745
373
 
746
374
  ```bash
747
- pnpm install
748
- pnpm build # clean + asc (5 .wasm files) + tsc → dist/ (no source maps)
749
- pnpm build:ts:debug # for debugging: recompile TS to dist/ with .js.map / .d.ts.map
750
- pnpm test # Node runner (vitest)
751
- pnpm test:browser # browser runner (happy-dom)
752
- pnpm bench # mitata micro-benchmarks (5 algorithm blocks)
753
- pnpm lint # biome (lint + format)
754
- pnpm typecheck # tsc --noEmit
375
+ pnpm lint && pnpm typecheck && pnpm test:unit
755
376
  ```
756
377
 
757
- `pnpm build` is the release path: it empties `dist/` first, and `tsconfig.build.json` turns off
758
- `sourceMap` / `declarationMap`, so the output carries neither `.map` files nor
759
- `//# sourceMappingURL=` comments. The reason: the published package ships only `dist/`, while a
760
- map's `sources` point at `../src/*.ts`, which is not published (and the maps embed no
761
- `sourcesContent`) — downstream can neither read the sources nor jump to definitions, yet pays
762
- roughly 35% extra size for it. For in-repo debugging use `pnpm build:ts:debug`
763
- (`tsconfig.build.debug.json`), which emits maps into `dist/`.
764
-
765
- **Every wasm module is built with `--optimize` only, no `--enable simd`**: SM4's speed
766
- comes from scalar multi-block interleaving and depends on no SIMD instruction set. All
767
- five algorithm modules therefore share the same runtime requirements, and `sm4.wasm`
768
- no longer requires **WASM SIMD**.
769
-
770
- Files under `src/**/assembly/**` are AssemblyScript sources compiled by
771
- `asc`. **Do not** use `import { type X }` in those files or let the
772
- formatter reorder imports — `asc` 0.28 doesn't recognize the type modifier
773
- (those rules are already turned off for the directory in the biome config).
774
-
775
- Each `src/*/assembly/` directory contains a single-line `tsconfig.json`
776
- that just `extends` the root `tsconfig.assembly.json`. It pulls in the AS
777
- type definitions **only so that IDEs type-check these files with AS
778
- semantics** — otherwise editing them reports `TS2304` for things like
779
- `usize`, `u32`, `StaticArray`. The root `tsconfig.json` `exclude`s the
780
- directory, so the CLI `tsc` doesn't touch them; these per-directory
781
- configs don't affect the build or typecheck.
782
-
783
378
  ---
784
379
 
785
380
  ## License
786
381
 
787
- [Apache-2.0](./LICENSE).
788
-
789
- The algorithm implementations in this project reference or are ported from
790
- the following Apache-2.0 projects; attribution details are in [`NOTICE`](./NOTICE):
791
-
792
- - [GmSSL](https://github.com/guanzhi/GmSSL) — the alignment baseline for
793
- SM2 / SM9 semantics and naming;
794
- - [gmkit / gmkitx](https://github.com/gmkits/gmkit) — the source the ZUC
795
- implementation was ported from.
796
-
797
- </details>
798
-
799
- ---
800
-
801
- <details>
802
- <summary><h2 id="ru-документация-на-русском" style="display:inline-block">🇷🇺 Русский</h2></summary>
803
-
804
- # sm-crypto-wasm
805
-
806
- > 📖 **Полная документация** (таблицы параметров, примеры и **интерактивные Playground**): <https://jktantan.github.io/sm-crypto-wasm/ru/>
807
-
808
- Реализация китайских криптографических стандартов (**SM2 / SM3 / SM4 / SM9 /
809
- ZUC**) на **AssemblyScript → WebAssembly**. Работает на Node 18+ и в
810
- современных браузерах (включая Web Workers). Алгоритмы разделены по
811
- подпакетам и подгружаются лениво.
812
-
813
- - **Производительность**: горячие пути (модульная арифметика больших чисел,
814
- сжатие хэша, раунды блочного шифра) выполняются в WASM. Лёгкие алгоритмы
815
- (SM3 / SM4 / ZUC) измеренно в 1.1~3.0× быстрее чистой JS-реализации; часть на
816
- эллиптических кривых (SM2) ещё оптимизируется — см. **Производительность** ниже;
817
- - **Без инструментария**: на выходе `.wasm` плюс тонкая TS-прослойка;
818
- пользователю не нужны `node-gyp`, Rust или компилятор;
819
- - **Кросс-рантайм**: один и тот же API работает в Node, браузере и
820
- Worker'е;
821
- - **Соответствие стандартам**: реализации следуют серии GM/T 0003 / 0004 /
822
- 0002 / 0044 / 0001 и сверяются с официальными векторами через
823
- OpenSSL / GmSSL / gmkitx.
824
-
825
- ---
826
-
827
- ## Производительность
828
-
829
- Горячие пути (модульная арифметика больших чисел, сжатие хэша, раунды блочного
830
- шифра, спаривания) выполняются внутри WASM. **Каждая операция, у которой есть
831
- чистый JS-аналог, быстрее его**: SM3 / ZUC — в 1.8–2.3×, шесть режимов SM4 —
832
- в 3.6–11.2×, SM2 — в 1.8–7.6×.
833
-
834
- Полная таблица по операциям, сравнение с `gmkitx` и разбор «почему именно так» —
835
- на **[странице производительности](https://jktantan.github.io/sm-crypto-wasm/ru/performance)**.
836
- Локально воспроизводится через `pnpm bench`.
837
-
838
- ## Установка
839
-
840
- ```bash
841
- pnpm add sm-crypto-wasm
842
- # либо: npm i sm-crypto-wasm / yarn add sm-crypto-wasm
843
- ```
844
-
845
- Требуется Node **>= 18** (в браузере — поддержка WebAssembly и
846
- `crypto.getRandomValues`).
847
-
848
- ---
849
-
850
- ## Быстрый старт
851
-
852
- > **Перед первым использованием каждого алгоритма нужен
853
- > `await algo.ensureLoaded()`** — инстанцирование WASM-модуля
854
- > асинхронное; после него все рабочие функции синхронные. Загружаются
855
- > только те `.wasm`, которые вы реально используете.
856
-
857
- ### SM3 — хеш и HMAC
858
-
859
- ```ts
860
- import { sm3 } from 'sm-crypto-wasm';
861
-
862
- await sm3.ensureLoaded();
863
- sm3.hash('616263'); // '66c7f0f4…8f4ba8e0' — hex-строка декодируется побайтово
864
- sm3.hash(new Uint8Array([0x61, 0x62, 0x63])); // тот же результат; байтовый ввод удобнее всего
865
- sm3.hmac('00112233445566778899aabbccddeeff', '616263');
866
- ```
867
-
868
- ### SM4 — симметричное шифрование (ECB / CBC / CTR / CFB / OFB / GCM)
869
-
870
- ```ts
871
- import { sm4 } from 'sm-crypto-wasm';
872
-
873
- await sm4.ensureLoaded();
874
- const key = '0123456789abcdeffedcba9876543210';
875
- const iv = '000102030405060708090a0b0c0d0e0f';
876
- const msg16 = '00112233445566778899aabbccddeeff'; // 16 байт
877
- const aad = 'aabbccdd'; // дополнительные аутентифицируемые данные (hex)
878
-
879
- sm4.encryptECB(key, msg16); // ECB без паддинга: ввод должен быть кратен 16 байтам
880
- sm4.encryptCBC(key, iv, '616263'); // CBC с PKCS#7-паддингом; допустима любая длина
881
- sm4.decryptCBC(key, iv, sm4.encryptCBC(key, iv, '616263'));
882
- sm4.encryptCTR(key, iv, '616263'); // CTR/OFB — потоковые режимы (алиасы cryptCTR / cryptOFB)
883
- const { ct, tag } = sm4.encryptGCM(key, iv, '616263', aad);
884
- sm4.decryptGCM(key, iv, ct, tag, aad); // при ошибке аутентификации бросает DecryptionError; можно передать { safeMode: true }
885
- ```
886
-
887
- ### SM2 — генерация ключей / подпись / проверка / шифрование
888
-
889
- ```ts
890
- import { sm2 } from 'sm-crypto-wasm';
891
-
892
- const { privateKeyPem, publicKeyPem } = await sm2.generateKeyPair(); // async; возвращает PKCS#8 / SPKI PEM
893
-
894
- const sig = sm2.sign(privateKeyPem, '616263'); // по умолчанию SM3withSM2; на выходе r||s в hex
895
- sm2.verify(publicKeyPem, '616263', sig); // → true
896
-
897
- const ct = sm2.encrypt(publicKeyPem, '616263'); // по умолчанию C1C3C2
898
- const ct2 = sm2.encrypt(publicKeyPem, '616263', { outputFormat: 'C1C2C3' });
899
- sm2.decrypt(privateKeyPem, ct); // на стороне расшифровки автоматически распознаётся C1C3C2 / C1C2C3
900
- sm2.decrypt(privateKeyPem, ct, { safeMode: true }); // при ошибке возвращает null вместо исключения
901
- ```
902
-
903
- Ключи также можно передавать как «сырой» hex или `Uint8Array` (32 байта для
904
- закрытого ключа, 64 или 65 байт для открытого).
905
-
906
- ### SM2 — инкапсуляция ключа и гибридное шифрование
907
-
908
- ```ts
909
- import { sm2 } from 'sm-crypto-wasm';
910
-
911
- await sm2.ensureLoaded();
912
-
913
- // KEM: защитить случайный симметричный ключ открытым ключом (16 или 32 байта).
914
- const { key, encapsulation } = sm2.kemEncrypt(publicKeyPem, 32);
915
- sm2.kemDecrypt(privateKeyPem, encapsulation, 32); // === key
916
-
917
- // Гибридное шифрование: один вызов выполняет KEM + SM4-GCM; подходит для больших сообщений.
918
- // Возвращает версионированный sealed: version || klen || encapLen || encapsulation || iv || tag || ct
919
- const sealed = sm2.hybridEncrypt(publicKeyPem, '616263');
920
- sm2.hybridDecrypt(privateKeyPem, sealed); // → '616263' (открытый текст в hex)
921
-
922
- // sealed можно представить в виде самоописывающего объекта.
923
- const obj = sm2.sealedToObject(sealed);
924
- sm2.hybridDecrypt(privateKeyPem, sm2.sealedFromObject(obj));
925
- ```
926
-
927
- - Каждый вызов `hybridEncrypt` создаёт новые 16-байтовый DEK и 12-байтовый IV; строки сообщений интерпретируются как **hex** (для пустого сообщения передайте `new Uint8Array(0)`).
928
- - Гибридному режиму нужны и `sm2.wasm`, и `sm4.wasm`; перед первым вызовом выполните `await Promise.all([sm2.ensureLoaded(), sm4.ensureLoaded()])`.
929
- - `hybridDecrypt` поддерживает `{ safeMode: true }` и возвращает `null` при ошибках, связанных с шифртекстом.
930
- - В SM2 нет стандартизованной конструкции KEM; `kemEncrypt` — общепринятый приём «зашифровать случайный ключ открытым ключом SM2».
931
-
932
- ### SM2 — протокол обмена ключами (GM/T 0003.3)
933
-
934
- ```ts
935
- import { sm2 } from 'sm-crypto-wasm';
936
-
937
- const A = await sm2.generateKeyPair();
938
- const B = await sm2.generateKeyPair();
939
-
940
- const alice = await sm2.createExchangeInitiator(A.privateKeyPem, B.publicKeyPem, {
941
- idSelf: 'Alice', idPeer: 'Bob',
942
- });
943
- const bob = await sm2.createExchangeResponder(B.privateKeyPem, A.publicKeyPem, {
944
- idSelf: 'Bob', idPeer: 'Alice',
945
- });
946
-
947
- const r1 = alice.initiate(); // инициатор → ответчик, первое сообщение
948
- const r2 = bob.respond(r1); // ответчик → инициатор, ответ
949
- const r3 = alice.confirm(r2); // подтверждение инициатора (содержит S_A для проверки ответчиком)
950
-
951
- alice.getSharedKey(r3); // обе стороны получают один и тот же общий секрет
952
- bob.getSharedKey(r3);
953
- ```
954
-
955
- ### SM9 — подпись / шифрование / инкапсуляция ключей (GM/T 0044)
956
-
957
- ```ts
958
- import { sm9 } from 'sm-crypto-wasm';
959
-
960
- // В SM9 два мастер-ключа (различаются по purpose): мастер-ключ подписи
961
- // использует мастер-публичный ключ в G2, мастер-ключ шифрования — в G1.
962
- const signKp = await sm9.generateKeyPair({ id: 'alice@foo.com' }); // по умолчанию purpose: 'sign'
963
- const encKp = await sm9.generateKeyPair({ id: 'alice@foo.com', purpose: 'enc' });
964
-
965
- const sig = sm9.sign(signKp.masterPrivatePem, 'alice@foo.com', '616263');
966
- sm9.verify(signKp.masterPublicPem, 'alice@foo.com', '616263', sig);
967
- // Первый аргумент verify / encrypt также принимает мастер-приватный ключ (для обратной совместимости).
968
-
969
- const ct = sm9.encrypt(encKp.masterPublicPem, 'alice@foo.com', '616263');
970
- sm9.decrypt(encKp.masterPrivatePem, 'alice@foo.com', ct); // → '616263'
971
-
972
- // Инкапсуляция ключа (KEM): согласование симметричного ключа без передачи сообщения.
973
- // Обратите внимание: первый аргумент — это *мастер-приватный ключ шифрования*
974
- // (KEM нуждается в нём, чтобы вывести Ppub-e; для encrypt выше допустим мастер-публичный ключ).
975
- const { key, encapsulation } = sm9.kemEncrypt(encKp.masterPrivatePem, 'alice@foo.com', 32);
976
- sm9.kemDecrypt(encKp.masterPrivatePem, 'alice@foo.com', encapsulation, 32); // === key
977
-
978
- // Гибридное шифрование: инкапсулировать 16-байтовый DEK мастер-публичным (или приватным)
979
- // ключом и id, затем зашифровать сообщение через SM4-GCM.
980
- const sealed = sm9.hybridEncrypt(encKp.masterPublicPem, 'alice@foo.com', '616263');
981
- sm9.hybridDecrypt(encKp.masterPrivatePem, 'alice@foo.com', sealed); // → '616263'
982
- ```
983
-
984
- > Гибридному режиму SM9 нужны и `sm9.wasm`, и `sm4.wasm`: перед первым вызовом выполните `await sm9.ensureLoaded()` и `await sm4.ensureLoaded()`.
985
-
986
- ### ZUC — конфиденциальность EEA3 и целостность EIA3
987
-
988
- ```ts
989
- import { zuc } from 'sm-crypto-wasm';
990
-
991
- await zuc.ensureLoaded();
992
- const key = '000102030405060708090a0b0c0d0e0f';
993
- const iv = '00000000000000000000000000000000';
994
-
995
- zuc.zucKeystream(key, iv, 16); // поток ключей (Uint8Array)
996
- zuc.zucEncrypt(key, iv, '616263'); // EEA3 — каждая тройка (key, iv, counter) может шифроваться лишь единожды
997
- zuc.zucDecrypt(key, iv, ct); // расшифровка (защита от повторного использования не действует)
998
-
999
- // Тег целостности EIA3 (32 бита); IV собирается из COUNT / BEARER / DIRECTION
1000
- const macIv = zuc.zucEia3Iv(0x66035492, 0x0f, 0);
1001
- zuc.zucMac(key, macIv, '616263'); // → 4-байтовый тег
1002
- ```
1003
-
1004
- ---
1005
-
1006
- ### X.509 · CMS · WebCrypto
1007
-
1008
- ```ts
1009
- import { x509, cms, webcrypto } from 'sm-crypto-wasm';
1010
-
1011
- const cert = x509.parseCertificate(pem);
1012
- const signed = cms.signData({ data, privateKey, certificate: cert });
1013
- const digest = await webcrypto.subtle.digest('SM3', data);
1014
- ```
1015
-
1016
- - **X.509** — разбор, проверка одного сертификата, самоподпись / выпуск через простой CA ([документация](https://jktantan.github.io/sm-crypto-wasm/ru/guide/x509));
1017
- - **CMS** — detached / attached SignedData, проверка по исходным DER `signedAttrs`, BER ([документация](https://jktantan.github.io/sm-crypto-wasm/ru/guide/cms));
1018
- - **WebCrypto** — подмножество в стиле `subtle`: SM3 / HMAC(SM3) / SM2 / SM4 ([документация](https://jktantan.github.io/sm-crypto-wasm/ru/guide/webcrypto)).
1019
-
1020
- Для X.509 / CMS нет проверки цепочки и CRL/OCSP; WebCrypto — не polyfill нативного `crypto.subtle`.
1021
-
1022
- ---
1023
-
1024
- ## Сводка API
1025
-
1026
- | Модуль | Функции |
1027
- |---|---|
1028
- | `sm2` | `generateKeyPair` (async) · `getPublicKey` · `sign` · `verify` · `encrypt` · `decrypt` · `preparePublicKey` (async) · `configurePreparedKeyCache` · `clearPreparedPublicKeys` · `getPreparedPublicKeys` · `kemEncrypt` · `kemDecrypt` · `hybridEncrypt` · `hybridDecrypt` · `sealedToObject` · `sealedFromObject` · `createExchangeInitiator` (async) · `createExchangeResponder` (async) |
1029
- | `webcrypto` | `subtle` · `CryptoKey` |
1030
- | `cms` | `signData` · `verifySignedData` · `parseSignedData` · `CmsError` |
1031
- | `x509` | `parseCertificate` · `parseCertificates` · `createSelfSignedCertificate` · `issueCertificate` · `parseDistinguishedName` · `verifyCertificateSignature` |
1032
- | `sm3` | `hash` · `hmac` |
1033
- | `sm4` | `encryptECB` / `decryptECB` · `encryptCBC` / `decryptCBC` · `encryptCTR` / `decryptCTR` · `encryptCFB` / `decryptCFB` · `encryptOFB` / `decryptOFB` · `encryptGCM` / `decryptGCM` |
1034
- | `sm9` | `generateKeyPair` (async) · `sign` · `verify` · `encrypt` · `decrypt` · `kemEncrypt` · `kemDecrypt` · `hybridEncrypt` · `hybridDecrypt` · `sealedToObject` · `sealedFromObject` · `getSignPublicKey` · `getEncPublicKey` · `getUserPublicKey` · `createExchangeInitiator` (async) · `createExchangeResponder` (async) |
1035
- | `zuc` | `zucKeystream` · `zucEncrypt` · `zucDecrypt` · `zucMac` · `zucEia3Iv` · `configureReuseGuard` |
1036
-
1037
- Документированные алиасы (указывают на ту же реализацию):
1038
- `sm4.encryptCTR` / `decryptCTR` — алиасы `cryptCTR`,
1039
- `encryptOFB` / `decryptOFB` — алиасы `cryptOFB`;
1040
- `zuc.encrypt` / `decrypt` / `generateKeystream` / `mac` / `eia3` — алиасы
1041
- `zucEncrypt` / `zucDecrypt` / `zucKeystream` / `zucMac`.
1042
-
1043
- ---
1044
-
1045
- ## Использование одного алгоритма (импорт по подпутям)
1046
-
1047
- В сценариях, критичных к размеру, можно обойти агрегированную точку входа
1048
- и подгружать только нужный `.wasm`:
1049
-
1050
- ```ts
1051
- import { hash } from 'sm-crypto-wasm/sm3';
1052
- import { encryptCBC } from 'sm-crypto-wasm/sm4';
1053
- ```
1054
-
1055
- Доступные подпути: `sm-crypto-wasm/sm2` · `/sm3` · `/sm4` · `/sm9` · `/x509` · `/cms` · `/webcrypto` · `/zuc`.
1056
-
1057
- ---
1058
-
1059
- ## Кросс-рантайм
1060
-
1061
- | Среда | Путь загрузки |
1062
- |---|---|
1063
- | Node 18+ | читает `dist/<algo>/<algo>.wasm` через `fs/promises`; доступен `loadWasmSync()` |
1064
- | Браузер (через сборщик) | `fetch(new URL('../<algo>/<algo>.wasm', import.meta.url))`; сборщик кладёт файл в вывод и переписывает URL |
1065
- | Web Worker | загрузчик не зависит от DOM и работает внутри Worker'а |
1066
- | Deno / Bun | идут по Node-пути |
1067
- | Встроенный браузер WeChat | как обычный браузер (`fetch` + `crypto.getRandomValues`); см. [WeChat и мини-программы](https://jktantan.github.io/sm-crypto-wasm/ru/guide/wechat) |
1068
- | Мини-программа WeChat | `WXWebAssembly` + `wx.getRandomValues`; см. [WeChat и мини-программы](https://jktantan.github.io/sm-crypto-wasm/ru/guide/wechat) |
1069
-
1070
- ### Поддержка сборщиков
1071
-
1072
- Пакет поставляет две реализации (Node-версия читает файлы, браузерная использует
1073
- `fetch`), а поле `browser` в `package.json` переключает их на этапе сборки. Поэтому
1074
- **`node:*` не попадает в браузерный бандл** — сборщики, которые не выносят встроенные
1075
- модули Node наружу (Webpack, esbuild, Rspack), больше не падают на этапе разбора
1076
- с `Can't resolve 'node:fs'`.
1077
-
1078
- | Сборщик | Нужна настройка? | Ресурс wasm |
1079
- |---|---|---|
1080
- | **Vite 5** | нет | автоматически попадает в вывод (`assets/*.wasm`) |
1081
- | **Webpack 5** | нет | автоматически выдаётся как asset |
1082
- | **Rspack 2** | нет | выдаётся как asset (имена с hash) |
1083
- | **Turbopack** (по умолчанию в Next.js 16) | нет | попадает в `static/media/*.wasm` |
1084
- | **esbuild** | одна строка плагина (ниже) | esbuild не обрабатывает файлы, на которые указывает `new URL(..., import.meta.url)` |
1085
- | **Node 18+** | нет | читает `dist/<algo>/<algo>.wasm` |
1086
-
1087
- Пользователи esbuild подключают небольшой встроенный плагин — он кладёт wasm туда,
1088
- откуда его берёт рантайм:
1089
-
1090
- ```ts
1091
- import { smCryptoWasm } from 'sm-crypto-wasm/esbuild-plugin';
1092
-
1093
- await esbuild.build({
1094
- entryPoints: ['src/main.ts'],
1095
- bundle: true,
1096
- platform: 'browser',
1097
- format: 'esm',
1098
- outdir: 'dist/assets', // сюда попадает выходной JS
1099
- plugins: [smCryptoWasm()], // → wasm копируется в dist/<algo>/<algo>.wasm
1100
- });
1101
- ```
1102
-
1103
- Не хотите плагин? Скопируйте `node_modules/sm-crypto-wasm/dist/{sm2,sm3,sm4,sm9,zuc}`
1104
- в **родительский** каталог выходного JS (в примере выше — `dist/`).
1105
-
1106
- Браузеру нужен **защищённый контекст** (`https://` или `localhost`) для
1107
- `crypto.getRandomValues`; если его нет, библиотека **бросает ошибку** (fail-closed), а не
1108
- откатывается к некриптографическому источнику.
1109
-
1110
- Среды со строго потоковой загрузкой WASM, такие как Cloudflare Workers / Vercel Edge,
1111
- **вне области поддержки**; загрузка «сырого» ESM напрямую через `<script type="module">`
1112
- (без сборщика) — тоже.
1113
-
1114
- ---
1115
-
1116
- ## Обработка ошибок
1117
-
1118
- Все ошибки вида «отклонено из-за ввода» — подклассы `SmCryptoError`, в двух
1119
- ветках:
1120
-
1121
- ```ts
1122
- import { SmCryptoError, InvalidKeyError, InvalidPublicKeyError,
1123
- CiphertextError, InvalidCiphertextError, DecryptionError } from 'sm-crypto-wasm';
1124
-
1125
- try {
1126
- sm2.decrypt(priv, ct);
1127
- } catch (e) {
1128
- if (e instanceof DecryptionError) { /* шифртекст не соответствует ключу */ }
1129
- else if (e instanceof CiphertextError) { /* проблема структуры или валидации */ }
1130
- else if (e instanceof InvalidKeyError) { /* формат / длина / кривая ключа */ }
1131
- else throw e;
1132
- }
1133
- ```
1134
-
1135
- - Ветка ключей: `InvalidKeyError` (с подклассом `InvalidPublicKeyError`);
1136
- - Ветка шифртекста: `CiphertextError` (с подклассом
1137
- `InvalidCiphertextError` для структурных ошибок и `DecryptionError` —
1138
- для ошибок валидации);
1139
- - Проблемы с типом или диапазоном (например, ограничения длины,
1140
- недопустимые значения перечислений) сохраняют нативные `TypeError` /
1141
- `RangeError` среды исполнения.
1142
-
1143
- ---
1144
-
1145
- ## Замечания по безопасности
1146
-
1147
- - **Используется только ГПСЧ среды исполнения**: `crypto.getRandomValues`
1148
- (браузеры / Node 19+) либо `node:crypto`-shim для Node 18. **Собственный
1149
- ГПСЧ в библиотеке не реализован.**
1150
- - **Закрытые ключи не пишутся на диск и не журналируются**: библиотека
1151
- не выполняет никакой сериализации ключей на диск и не пишет их в логи.
1152
- - **Защита от повторного использования ZUC**: повторное шифрование с той
1153
- же тройкой `(key, iv, counter)` приводит к исключению (повторное
1154
- использование потока ключей — инцидент конфиденциальности). Если
1155
- счётчик ведётся внешне, защиту можно отключить глобально через
1156
- `configureReuseGuard({ enabled: false })` либо разово передав
1157
- `{ reuseGuard: false }`.
1158
- - **Корректность открытого ключа SM2**: `encrypt` бросает
1159
- `InvalidPublicKeyError` для точек вне кривой; `verify` возвращает
1160
- просто `false`.
1161
- - Нет интеграции с криптографическим оборудованием / HSM / PKCS#11 /
1162
- программными сертификатами; форматы ключей за пределами PEM не
1163
- разбираются.
1164
-
1165
- ---
1166
-
1167
- ## Разработка
1168
-
1169
- ```bash
1170
- pnpm install
1171
- pnpm build # clean + asc (5 .wasm-файлов) + tsc → dist/ (без source map)
1172
- pnpm build:ts:debug # для отладки: пересборка TS в dist/ с .js.map / .d.ts.map
1173
- pnpm test # Node-раннер (vitest)
1174
- pnpm test:browser # браузерный раннер (happy-dom)
1175
- pnpm bench # микро-бенчмарки mitata (5 блоков алгоритмов)
1176
- pnpm lint # biome (lint + format)
1177
- pnpm typecheck # tsc --noEmit
1178
- ```
1179
-
1180
- `pnpm build` — это путь релиза: он сначала очищает `dist/`, а `tsconfig.build.json` отключает
1181
- `sourceMap` / `declarationMap`, поэтому в артефактах нет ни `.map`, ни комментариев
1182
- `//# sourceMappingURL=`. Причина: в пакет попадает только `dist/`, а `sources` в map указывают
1183
- на `../src/*.ts`, которых в пакете нет (и `sourcesContent` не встроен) — потребитель не получит ни
1184
- исходников, ни перехода к определениям, но заплатит примерно +35% размера. Для отладки внутри
1185
- репозитория используйте `pnpm build:ts:debug` (`tsconfig.build.debug.json`) — он кладёт map в
1186
- `dist/`.
1187
-
1188
- **Все wasm-модули собираются только с `--optimize`, без `--enable simd`**: скорость SM4
1189
- даёт скалярное чередование блоков, которое не зависит ни от какого набора SIMD-инструкций.
1190
- Поэтому все пять модулей предъявляют одинаковые требования к среде, и `sm4.wasm` больше
1191
- не требует **WASM SIMD**.
1192
-
1193
- Файлы в `src/**/assembly/**` — это исходники AssemblyScript, компилируемые
1194
- `asc`. **Не используйте** в них `import { type X }` и не позволяйте
1195
- форматтеру переставлять импорты — `asc` 0.28 не понимает модификатор типа
1196
- (соответствующие правила уже отключены для этого каталога в конфиге
1197
- biome).
1198
-
1199
- В каждом каталоге `src/*/assembly/` лежит одно-строчный `tsconfig.json`,
1200
- который лишь `extends` корневой `tsconfig.assembly.json`. Он подтягивает
1201
- определения типов AS **исключительно ради того, чтобы IDE проверяла эти
1202
- файлы по семантике AS** — иначе при редактировании появляется `TS2304`
1203
- для `usize`, `u32`, `StaticArray` и т. п. Корневой `tsconfig.json`
1204
- `exclude`ит этот каталог, поэтому CLI-`tsc` их не трогает; эти
1205
- конфигурации не влияют ни на сборку, ни на проверку типов.
1206
-
1207
- ---
1208
-
1209
- ## Лицензия
1210
-
1211
- [Apache-2.0](./LICENSE).
1212
-
1213
- Реализации алгоритмов в этом проекте ссылаются на или портированы из
1214
- следующих проектов под Apache-2.0; сведения об авторстве — в
1215
- [`NOTICE`](./NOTICE):
382
+ [Apache-2.0](./LICENSE)。
1216
383
 
1217
- - [GmSSL](https://github.com/guanzhi/GmSSL) — базовый источник семантики
1218
- и наименований SM2 / SM9;
1219
- - [gmkit / gmkitx](https://github.com/gmkits/gmkit) — источник, из
1220
- которого портирована реализация ZUC.
384
+ 本项目的算法实现参考/移植了以下同样以 Apache-2.0 发布的项目,归属声明见 [`NOTICE`](./NOTICE):
1221
385
 
1222
- </details>
386
+ - [GmSSL](https://github.com/guanzhi/GmSSL) —— SM2 / SM9 的算法语义与命名对齐基准;
387
+ - [gmkit / gmkitx](https://github.com/gmkits/gmkit) —— ZUC 实现的移植来源。