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