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.
- package/README.md +62 -834
- package/dist/asn1/decode.d.ts +165 -0
- package/dist/asn1/decode.js +457 -0
- package/dist/asn1/encode.d.ts +169 -0
- package/dist/asn1/encode.js +414 -0
- package/dist/asn1/index.d.ts +17 -0
- package/dist/asn1/index.js +17 -0
- package/dist/asn1/oid.d.ts +40 -0
- package/dist/asn1/oid.js +136 -0
- package/dist/asn1/time.d.ts +71 -0
- package/dist/asn1/time.js +165 -0
- package/dist/asn1/types.d.ts +160 -0
- package/dist/asn1/types.js +112 -0
- package/dist/cms/errors.d.ts +23 -0
- package/dist/cms/errors.js +17 -0
- package/dist/cms/index.d.ts +18 -0
- package/dist/cms/index.js +15 -0
- package/dist/cms/oids.d.ts +47 -0
- package/dist/cms/oids.js +47 -0
- package/dist/cms/parse.d.ts +217 -0
- package/dist/cms/parse.js +420 -0
- package/dist/cms/sign.d.ts +113 -0
- package/dist/cms/sign.js +194 -0
- package/dist/cms/verify.d.ts +117 -0
- package/dist/cms/verify.js +222 -0
- package/dist/common/hybrid.d.ts +25 -0
- package/dist/common/hybrid.js +25 -0
- package/dist/common/sealed.d.ts +119 -15
- package/dist/common/sealed.js +67 -9
- package/dist/esbuild-plugin.d.ts +74 -5
- package/dist/esbuild-plugin.js +69 -4
- package/dist/index.d.ts +22 -0
- package/dist/index.js +25 -0
- package/dist/pem/asn1.d.ts +19 -65
- package/dist/pem/asn1.js +19 -288
- package/dist/pem/index.d.ts +2 -0
- package/dist/pem/index.js +2 -0
- package/dist/pem/pkcs8.d.ts +64 -8
- package/dist/pem/pkcs8.js +75 -7
- package/dist/pem/sm2.d.ts +90 -2
- package/dist/pem/sm2.js +204 -15
- package/dist/pem/sm9.d.ts +42 -2
- package/dist/pem/sm9.js +42 -2
- package/dist/runtime/base64.core.d.ts +25 -1
- package/dist/runtime/base64.core.js +31 -1
- package/dist/runtime/convert.d.ts +70 -7
- package/dist/runtime/convert.js +129 -9
- package/dist/runtime/errors.d.ts +64 -8
- package/dist/runtime/errors.js +64 -8
- package/dist/runtime/index.d.ts +5 -0
- package/dist/runtime/index.js +5 -0
- package/dist/runtime/loadWasm.browser.d.ts +33 -0
- package/dist/runtime/loadWasm.browser.js +46 -0
- package/dist/runtime/loadWasm.core.d.ts +59 -3
- package/dist/runtime/loadWasm.core.js +53 -3
- package/dist/runtime/loadWasm.d.ts +44 -0
- package/dist/runtime/loadWasm.js +79 -4
- package/dist/runtime/random.browser.d.ts +24 -0
- package/dist/runtime/random.browser.js +31 -1
- package/dist/runtime/random.core.d.ts +21 -0
- package/dist/runtime/random.core.js +27 -0
- package/dist/runtime/random.d.ts +28 -0
- package/dist/runtime/random.js +40 -1
- package/dist/sm2/exchange.d.ts +132 -16
- package/dist/sm2/exchange.js +120 -9
- package/dist/sm2/hybrid.d.ts +65 -3
- package/dist/sm2/hybrid.js +53 -1
- package/dist/sm2/index.d.ts +324 -16
- package/dist/sm2/index.js +530 -19
- package/dist/sm2/kem.d.ts +62 -5
- package/dist/sm2/kem.js +37 -2
- package/dist/sm2/keyInput.d.ts +36 -3
- package/dist/sm2/keyInput.js +36 -3
- package/dist/sm2/sm2.wasm +0 -0
- package/dist/sm3/index.d.ts +30 -0
- package/dist/sm3/index.js +55 -1
- package/dist/sm4/index.d.ts +125 -17
- package/dist/sm4/index.js +201 -14
- package/dist/sm9/core.d.ts +58 -8
- package/dist/sm9/core.js +36 -5
- package/dist/sm9/hybrid.d.ts +66 -3
- package/dist/sm9/hybrid.js +46 -1
- package/dist/sm9/index.d.ts +265 -19
- package/dist/sm9/index.js +175 -5
- package/dist/sm9/input.d.ts +37 -2
- package/dist/sm9/input.js +50 -1
- package/dist/sm9/kem.d.ts +57 -4
- package/dist/sm9/kem.js +47 -1
- package/dist/webcrypto/algorithms.d.ts +88 -0
- package/dist/webcrypto/algorithms.js +168 -0
- package/dist/webcrypto/cryptoKey.d.ts +130 -0
- package/dist/webcrypto/cryptoKey.js +167 -0
- package/dist/webcrypto/errors.d.ts +43 -0
- package/dist/webcrypto/errors.js +69 -0
- package/dist/webcrypto/index.d.ts +24 -0
- package/dist/webcrypto/index.js +21 -0
- package/dist/webcrypto/subtle.d.ts +118 -0
- package/dist/webcrypto/subtle.js +426 -0
- package/dist/x509/encode.d.ts +129 -0
- package/dist/x509/encode.js +248 -0
- package/dist/x509/errors.d.ts +29 -0
- package/dist/x509/errors.js +29 -0
- package/dist/x509/extensions.d.ts +66 -0
- package/dist/x509/extensions.js +273 -0
- package/dist/x509/index.d.ts +21 -0
- package/dist/x509/index.js +30 -0
- package/dist/x509/ip.d.ts +21 -0
- package/dist/x509/ip.js +122 -0
- package/dist/x509/issue.d.ts +87 -0
- package/dist/x509/issue.js +521 -0
- package/dist/x509/oids.d.ts +74 -0
- package/dist/x509/oids.js +103 -0
- package/dist/x509/parse.d.ts +45 -0
- package/dist/x509/parse.js +550 -0
- package/dist/x509/sha1.d.ts +20 -0
- package/dist/x509/sha1.js +89 -0
- package/dist/x509/sha256.d.ts +20 -0
- package/dist/x509/sha256.js +114 -0
- package/dist/x509/sig.d.ts +35 -0
- package/dist/x509/sig.js +104 -0
- package/dist/x509/types.d.ts +490 -0
- package/dist/x509/types.js +13 -0
- package/dist/x509/verify.d.ts +91 -0
- package/dist/x509/verify.js +105 -0
- package/dist/zuc/index.d.ts +128 -10
- package/dist/zuc/index.js +136 -7
- package/package.json +57 -28
package/README.md
CHANGED
|
@@ -1,54 +1,28 @@
|
|
|
1
1
|
# sm-crypto-wasm
|
|
2
2
|
|
|
3
|
-
[
|
|
3
|
+
[](https://www.npmjs.com/package/sm-crypto-wasm)
|
|
4
|
+
[](https://www.npmjs.com/package/sm-crypto-wasm)
|
|
5
|
+
[](https://github.com/jktantan/sm-crypto-wasm/blob/main/LICENSE)
|
|
6
|
+
[](https://nodejs.org/)
|
|
4
7
|
|
|
5
|
-
|
|
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
|
-
|
|
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
|
-
|
|
51
|
-
# 或
|
|
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
|
|
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
|
-
##
|
|
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
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
- [
|
|
1155
|
-
и наименований SM2 / SM9;
|
|
1156
|
-
- [gmkit / gmkitx](https://github.com/gmkits/gmkit) — источник, из
|
|
1157
|
-
которого портирована реализация ZUC.
|
|
384
|
+
本项目的算法实现参考/移植了以下同样以 Apache-2.0 发布的项目,归属声明见 [`NOTICE`](./NOTICE):
|
|
1158
385
|
|
|
1159
|
-
|
|
386
|
+
- [GmSSL](https://github.com/guanzhi/GmSSL) —— SM2 / SM9 的算法语义与命名对齐基准;
|
|
387
|
+
- [gmkit / gmkitx](https://github.com/gmkits/gmkit) —— ZUC 实现的移植来源。
|