Browse Source

feat: add crypto-wasm package for SM4 encryption/decryption, update dependencies, and enhance build scripts

main
谢亚昕 19 hours ago
parent
commit
96c19502bd
  1. 5
      .gitignore
  2. 3
      .vscode/settings.json
  3. 40
      buildin/dm/src/index.ts
  4. 20
      bun.lock
  5. 3
      packages/core/vitest.config.ts
  6. 137
      packages/crypto-wasm/Cargo.lock
  7. 12
      packages/crypto-wasm/Cargo.toml
  8. 22
      packages/crypto-wasm/README.md
  9. 35
      packages/crypto-wasm/package.json
  10. 136
      packages/crypto-wasm/src/lib.rs
  11. 9
      packages/crypto-wasm/vitest.config.ts
  12. 5
      packages/dx/vitest.config.ts
  13. 8
      packages/example/package.json
  14. 20
      packages/example/src/index.ts
  15. 2
      packages/example/src/sm4.d.ts
  16. 59
      packages/example/src/sm4.js
  17. 6
      packages/example/vite.config.ts
  18. 6
      readme.md
  19. 5
      tsconfig.base.json

5
.gitignore

@ -1,2 +1,5 @@
node_modules
dist
dist
packages/*-wasm/target
packages/*-wasm/pkg

3
.vscode/settings.json

@ -6,5 +6,6 @@
"editor.codeActionsOnSave": {
"source.fixAll.oxc": "explicit"
},
"js/ts.tsdk.path": "./node_modules/typescript/lib"
"js/ts.tsdk.path": "./node_modules/typescript/lib",
"typescript.tsdk": "./node_modules/typescript/lib"
}

40
buildin/dm/src/index.ts

@ -1,8 +1,11 @@
import cac from 'cac'
import pkg from "../package.json"
import { build } from "tsdown"
import { exec } from 'child_process'
import { promisify } from 'util'
const cli = cac()
const execAsync = promisify(exec)
cli.version(pkg.version)
@ -12,29 +15,38 @@ cli.option('--entry <entryPath>', 'Choose entry path', {
default: 'src/index.ts',
})
cli.command('dev [module]', '开发').action((_, options) => {
build({
const createSharedBuildConfig = (entryPath?: string) => ({
entry: entryPath ? [entryPath] : undefined,
sourcemap: false,
outExtensions: () => ({ js: '.js', dts: '.d.ts' }),
})
cli.command('dev [module]', '开发').action(async (_module, options) => {
await build({
...createSharedBuildConfig(options.entry),
watch: true,
entry: options.entry ? [options.entry] : undefined,
sourcemap: false,
dts: false,
outExtensions: () => {
return { js: '.js', dts: '.d.ts' }
}
})
})
cli.command('build [module]', '构建').action((_, options) => {
build({
entry: options.entry ? [options.entry] : undefined,
sourcemap: false,
cli.command('build [module]', '构建').action(async (_module, options) => {
await build({
...createSharedBuildConfig(options.entry),
dts: true,
format: ['esm', 'cjs'],
outExtensions: () => {
return { js: '.js', dts: '.d.ts' }
}
})
})
cli.command('wasm [module]', 'wasm')
.option('--cmd <command>', '执行命令')
.action(async (_module, options) => {
if (!options.cmd) {
throw new Error('请通过 --cmd 传入要执行的命令')
}
const { stdout, stderr } = await execAsync(options.cmd)
if (stdout) process.stdout.write(stdout)
if (stderr) process.stderr.write(stderr)
})
cli.parse()

20
bun.lock

@ -8,7 +8,7 @@
"@changesets/cli": "^2.30.0",
"@prettier/plugin-oxc": "^0.1.3",
"@types/node": "^22.7.9",
"dm": "workspace:*",
"dm": "workspace",
"lefthook": "^2.1.5",
"oxlint": "^1.59.0",
"tsdown": "^0.21.8",
@ -31,6 +31,10 @@
"name": "@dm/core",
"version": "0.0.1-alpha.1",
},
"packages/crypto-wasm": {
"name": "@dm/crypto-wasm",
"version": "0.0.1-alpha.1",
},
"packages/dx": {
"name": "@dm/dx",
"version": "0.0.1-alpha.1",
@ -42,10 +46,14 @@
"name": "example",
"dependencies": {
"@dm/core": "workspace:*",
"@dm/crypto-wasm": "workspace:*",
"@dm/dx": "workspace:*",
"crypto-js": "4.2.0",
"sm-crypto": "0.4.0",
},
"devDependencies": {
"vite": "^8.0.8",
"vite-plugin-wasm": "^3.6.0",
},
},
},
@ -102,6 +110,8 @@
"@dm/core": ["@dm/core@workspace:packages/core"],
"@dm/crypto-wasm": ["@dm/crypto-wasm@workspace:packages/crypto-wasm"],
"@dm/dx": ["@dm/dx@workspace:packages/dx"],
"@emnapi/core": ["@emnapi/core@1.9.2", "", { "dependencies": { "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" } }, "sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA=="],
@ -306,6 +316,8 @@
"cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
"crypto-js": ["crypto-js@4.2.0", "", {}, "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q=="],
"defu": ["defu@6.1.7", "", {}, "sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ=="],
"detect-indent": ["detect-indent@6.1.0", "", {}, "sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA=="],
@ -380,6 +392,8 @@
"js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="],
"jsbn": ["jsbn@1.1.0", "", {}, "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A=="],
"jsesc": ["jsesc@3.1.0", "", { "bin": { "jsesc": "bin/jsesc" } }, "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA=="],
"jsonfile": ["jsonfile@4.0.0", "", { "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg=="],
@ -514,6 +528,8 @@
"slash": ["slash@3.0.0", "", {}, "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q=="],
"sm-crypto": ["sm-crypto@0.4.0", "", { "dependencies": { "jsbn": "^1.1.0" } }, "sha512-OexH2V1EqmhXuOIPGoCl55OjMF0wwPUM/zhUjT0Q6vHBeopSRvTNRy76/1eRoFs3VBKt39hdFnxwpFmooHYa2A=="],
"source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="],
"spawndamnit": ["spawndamnit@3.0.1", "", { "dependencies": { "cross-spawn": "^7.0.5", "signal-exit": "^4.0.1" } }, "sha512-MmnduQUuHCoFckZoWnXsTg7JaiLBJrKFj9UI2MbRPGaJeVpsLcVBu6P/IGZovziM/YBsellCmsprgNA+w0CzVg=="],
@ -558,6 +574,8 @@
"vite": ["vite@8.0.8", "", { "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.4", "postcss": "^8.5.8", "rolldown": "1.0.0-rc.15", "tinyglobby": "^0.2.15" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.1.0", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-dbU7/iLVa8KZALJyLOBOQ88nOXtNG8vxKuOT4I2mD+Ya70KPceF4IAmDsmU0h1Qsn5bPrvsY9HJstCRh3hG6Uw=="],
"vite-plugin-wasm": ["vite-plugin-wasm@3.6.0", "", { "peerDependencies": { "vite": "^2 || ^3 || ^4 || ^5 || ^6 || ^7 || ^8" } }, "sha512-mL/QPziiIA4RAA6DkaZZzOstdwbW5jO4Vz7Zenj0wieKWBlNvIvX5L5ljum9lcUX0ShNfBgCNLKTjNkRVVqcsw=="],
"vitest": ["vitest@4.1.4", "", { "dependencies": { "@vitest/expect": "4.1.4", "@vitest/mocker": "4.1.4", "@vitest/pretty-format": "4.1.4", "@vitest/runner": "4.1.4", "@vitest/snapshot": "4.1.4", "@vitest/spy": "4.1.4", "@vitest/utils": "4.1.4", "es-module-lexer": "^2.0.0", "expect-type": "^1.3.0", "magic-string": "^0.30.21", "obug": "^2.1.1", "pathe": "^2.0.3", "picomatch": "^4.0.3", "std-env": "^4.0.0-rc.1", "tinybench": "^2.9.0", "tinyexec": "^1.0.2", "tinyglobby": "^0.2.15", "tinyrainbow": "^3.1.0", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0", "why-is-node-running": "^2.3.0" }, "peerDependencies": { "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^20.0.0 || ^22.0.0 || >=24.0.0", "@vitest/browser-playwright": "4.1.4", "@vitest/browser-preview": "4.1.4", "@vitest/browser-webdriverio": "4.1.4", "@vitest/coverage-istanbul": "4.1.4", "@vitest/coverage-v8": "4.1.4", "@vitest/ui": "4.1.4", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@opentelemetry/api", "@types/node", "@vitest/browser-playwright", "@vitest/browser-preview", "@vitest/browser-webdriverio", "@vitest/coverage-istanbul", "@vitest/coverage-v8", "@vitest/ui", "happy-dom", "jsdom"], "bin": { "vitest": "vitest.mjs" } }, "sha512-tFuJqTxKb8AvfyqMfnavXdzfy3h3sWZRWwfluGbkeR7n0HUev+FmNgZ8SDrRBTVrVCjgH5cA21qGbCffMNtWvg=="],
"which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],

3
packages/core/vitest.config.ts

@ -6,8 +6,5 @@ export default defineProject({
exclude: [],
include: ['src/**/*.test.ts'],
environment: 'node',
alias: {
"@": "./src"
},
},
})

137
packages/crypto-wasm/Cargo.lock

@ -0,0 +1,137 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "base64"
version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
[[package]]
name = "bumpalo"
version = "3.20.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d20789868f4b01b2f2caec9f5c4e0213b41e3e5702a50157d699ae31ced2fcb"
[[package]]
name = "cfg-if"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
[[package]]
name = "crypto"
version = "0.0.0"
dependencies = [
"base64",
"gm-sm4",
"wasm-bindgen",
]
[[package]]
name = "gm-sm4"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b56818bef522abcd7de70e21f1ffcea13979d946a75ccf7c038a291a0ef92f6"
dependencies = [
"hex",
]
[[package]]
name = "hex"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70"
[[package]]
name = "once_cell"
version = "1.21.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50"
[[package]]
name = "proc-macro2"
version = "1.0.106"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "41f2619966050689382d2b44f664f4bc593e129785a36d6ee376ddf37259b924"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rustversion"
version = "1.0.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b39cdef0fa800fc44525c84ccb54a029961a8215f9619753635a9c0d2538d46d"
[[package]]
name = "syn"
version = "2.0.117"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e665b8803e7b1d2a727f4023456bbbbe74da67099c585258af0ad9c5013b9b99"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "unicode-ident"
version = "1.0.24"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75"
[[package]]
name = "wasm-bindgen"
version = "0.2.118"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0bf938a0bacb0469e83c1e148908bd7d5a6010354cf4fb73279b7447422e3a89"
dependencies = [
"cfg-if",
"once_cell",
"rustversion",
"wasm-bindgen-macro",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.118"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eeff24f84126c0ec2db7a449f0c2ec963c6a49efe0698c4242929da037ca28ed"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
]
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.118"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d08065faf983b2b80a79fd87d8254c409281cf7de75fc4b773019824196c904"
dependencies = [
"bumpalo",
"proc-macro2",
"quote",
"syn",
"wasm-bindgen-shared",
]
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.118"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5fd04d9e306f1907bd13c6361b5c6bfc7b3b3c095ed3f8a9246390f8dbdee129"
dependencies = [
"unicode-ident",
]

12
packages/crypto-wasm/Cargo.toml

@ -0,0 +1,12 @@
[package]
name = "crypto"
edition = "2021"
description = "SM4 encrypt/decrypt for WebAssembly (wasm-bindgen)"
[lib]
crate-type = ["cdylib"]
[dependencies]
wasm-bindgen = "0.2"
gm-sm4 = "0.10"
base64 = "0.22"

22
packages/crypto-wasm/README.md

@ -0,0 +1,22 @@
# crypto-wasm
### JavaScript / TypeScript 调用
```ts
import { encrypt, decrypt } from '@dm/crypto-wasm';
function main() {
const cipher = encrypt('hello world');
const plain = decrypt(cipher);
console.log(cipher); // Base64
console.log(plain); // hello world
}
main();
```
### 3) API
- `encrypt(data: string): string`:返回 Base64 密文
- `decrypt(data: string): string`:返回明文字符串

35
packages/crypto-wasm/package.json

@ -0,0 +1,35 @@
{
"name": "@dm/crypto-wasm",
"type": "module",
"description": "SM4 encrypt/decrypt for WebAssembly (wasm-bindgen)",
"version": "0.0.1-alpha.1",
"scripts": {
"build": "dm wasm --cmd 'wasm-pack build --scope dm-wasm'"
},
"files": [
"./pkg/crypto_bg.js",
"./pkg/crypto_bg.wasm",
"./pkg/crypto_bg.wasm.d.ts",
"./pkg/crypto.d.ts",
"./pkg/crypto.js"
],
"main": "./pkg/crypto.js",
"types": "./pkg/crypto.d.ts",
"sideEffects": [
"./pkg/crypto.js",
"./pkg/snippets/*"
],
"exports": {
".": {
"import": "./pkg/crypto.js",
"types": "./pkg/crypto.d.ts"
},
"./crypto.wasm": {
"import": "./pkg/crypto_bg.wasm",
"types": "./pkg/crypto_bg.wasm.d.ts"
},
"./crypto_bg.js": {
"import": "./pkg/crypto_bg.js"
}
}
}

136
packages/crypto-wasm/src/lib.rs

@ -0,0 +1,136 @@
//! SM4 ECB + PKCS#7,与 `sm-crypto`(JuneAndGreen)默认行为一致:`sm4.encrypt` / `sm4.decrypt` 且未指定 `mode: 'cbc'` 时为 ECB。
//! 密文对外格式:与示例中 `CryptoJS.enc.Base64.stringify(CryptoJS.enc.Hex.parse(hex))` 相同,即对原始密文字节做标准 Base64。
use base64::engine::general_purpose::STANDARD;
use base64::Engine;
use gm_sm4::Sm4Cipher;
use wasm_bindgen::prelude::*;
/// 与 JS 中 `SM4_KEY_BASE64` 一致
const SM4_KEY_BASE64: &str = "aceyfpZM9MWztRrzSCi/nA==";
fn sm4_key_16() -> Result<[u8; 16], ()> {
let raw = STANDARD.decode(SM4_KEY_BASE64).map_err(|_| ())?;
if raw.len() != 16 {
return Err(());
}
let mut k = [0u8; 16];
k.copy_from_slice(&raw);
Ok(k)
}
fn pkcs7_pad(mut data: Vec<u8>) -> Vec<u8> {
let pad_len = 16 - (data.len() % 16);
data.extend(std::iter::repeat(pad_len as u8).take(pad_len));
data
}
fn pkcs7_unpad(data: &[u8]) -> Result<Vec<u8>, ()> {
if data.is_empty() || data.len() % 16 != 0 {
return Err(());
}
let pad_len = *data.last().unwrap() as usize;
if pad_len == 0 || pad_len > 16 {
return Err(());
}
if data[data.len() - pad_len..]
.iter()
.any(|&b| b as usize != pad_len)
{
return Err(());
}
Ok(data[..data.len() - pad_len].to_vec())
}
fn sm4_encrypt_ecb(plain: &[u8], key: &[u8; 16]) -> Result<Vec<u8>, ()> {
let cipher = Sm4Cipher::new(key.as_slice()).map_err(|_| ())?;
let padded = pkcs7_pad(plain.to_vec());
let mut out = Vec::with_capacity(padded.len());
for chunk in padded.chunks_exact(16) {
let block = cipher.encrypt(chunk).map_err(|_| ())?;
out.extend_from_slice(&block);
}
Ok(out)
}
fn sm4_decrypt_ecb(cipher_bytes: &[u8], key: &[u8; 16]) -> Result<Vec<u8>, ()> {
if cipher_bytes.len() % 16 != 0 {
return Err(());
}
let cipher = Sm4Cipher::new(key.as_slice()).map_err(|_| ())?;
let mut out = Vec::with_capacity(cipher_bytes.len());
for chunk in cipher_bytes.chunks_exact(16) {
let block = cipher.decrypt(chunk).map_err(|_| ())?;
out.extend_from_slice(&block);
}
pkcs7_unpad(&out)
}
/// 加密:明文为 UTF-8 字符串(与 `sm-crypto` 对 string 输入按 UTF-8 编码一致);成功返回 Base64,失败返回空串。
#[wasm_bindgen(js_name = encrypt)]
pub fn encrypt(data: &str) -> String {
if data.is_empty() {
return String::new();
}
let Ok(key) = sm4_key_16() else {
return String::new();
};
match sm4_encrypt_ecb(data.as_bytes(), &key) {
Ok(ct) => STANDARD.encode(ct),
Err(()) => String::new(),
}
}
/// 解密:输入为 Base64 密文;成功返回 UTF-8 明文(非法 UTF-8 字节用替换符处理,与 JS `String.fromCharCode` 式解码有差异时以可显示为准),失败返回空串。
#[wasm_bindgen(js_name = decrypt)]
pub fn decrypt(data: &str) -> String {
if data.is_empty() {
return String::new();
}
let Ok(key) = sm4_key_16() else {
return String::new();
};
let ct = match STANDARD.decode(data) {
Ok(b) => b,
Err(_) => return String::new(),
};
match sm4_decrypt_ecb(&ct, &key) {
Ok(pt) => String::from_utf8_lossy(&pt).into_owned(),
Err(()) => String::new(),
}
}
#[cfg(test)]
mod tests {
use super::{decrypt, encrypt};
#[test]
fn round_trip_ascii() {
let plain = "hello world";
let enc = encrypt(plain);
assert!(!enc.is_empty(), "encrypt should succeed");
assert_eq!(decrypt(&enc), plain);
}
#[test]
fn round_trip_unicode() {
let plain = "你好 sm4";
let enc = encrypt(plain);
assert!(!enc.is_empty());
assert_eq!(decrypt(&enc), plain);
}
#[test]
fn empty_plaintext() {
assert_eq!(encrypt(""), "");
}
/// 与 `sm-crypto@0.4.0` + `crypto-js@4.2.0`(`sm4.js` 同款封装)对该明文的 Base64 一致,防止与前端漂移。
#[test]
fn encrypt_matches_sm_crypto_fixture() {
let plain = r#"{"username":"admin","password":"admin123","code":"35","uuid":"a70a4e306dde4abe889f3faec3a219a3","clientId":"ihF3cJj0Mxf0xYh9rUqrtcJs-f7iQMrF","clientSecret":"-hs4ENSrdMPGhJUxM3E4CogQaGGb_TCReB51KlWxklhJDP-Dkz5JgFTozeshWdqF1UFjKCOiKR--ny53tusqhw"}"#;
let expected = "ryfanf2S7ADSCLhO1rCXLZxHYuPGMXJKJMdUfVzKTKGnsgdccIqABqviVPXsTAnK785eNkSeTa6Mt6FzbrZa0kLQ+QTbMGE33dEDAkcYj03V4grD8GVrT575MFGt78RMs78jPRFWCLRiOAFEmrSKTPaMX3u5zCq+F6Bwkg5S0KmmAwUEYnnL5STqRJpK+d86MRhKDQpNvzgkR4iJYXCBSi5SxtD7dH4uXtdOz/NlJZ7tPMYJ2BD83e2PzjTe2dxJlTx+cDAPfbruJOeivTNayiM3QevGtfbsJHRPy/Ge7dwyqcJSGVkcUiLvzkANBfiZ6YBUwSopYXY2aLKS5fIGWg==";
assert_eq!(encrypt(plain), expected);
assert_eq!(decrypt(expected), plain);
}
}

9
packages/crypto-wasm/vitest.config.ts

@ -0,0 +1,9 @@
import { defineProject } from 'vitest/config'
export default defineProject({
test: {
name: "crypto-wasm",
exclude: [],
include: ['**/*.test.ts'],
},
})

5
packages/dx/vitest.config.ts

@ -2,12 +2,9 @@ import { defineProject } from 'vitest/config'
export default defineProject({
test: {
name: "core",
name: "dx",
exclude: [],
include: ['src/**/*.test.ts'],
environment: 'node',
alias: {
"@": "./src"
},
},
})

8
packages/example/package.json

@ -5,10 +5,14 @@
"dev": "vite"
},
"dependencies": {
"@dm/crypto-wasm": "workspace:*",
"@dm/core": "workspace:*",
"@dm/dx": "workspace:*"
"@dm/dx": "workspace:*",
"crypto-js": "4.2.0",
"sm-crypto": "0.4.0"
},
"devDependencies": {
"vite": "^8.0.8"
"vite": "^8.0.8",
"vite-plugin-wasm": "^3.6.0"
}
}

20
packages/example/src/index.ts

@ -1,11 +1,17 @@
import fire from "@dm/core"
import { decrypt, encrypt } from "@dm/crypto-wasm";
import { encrypt as sm4Encrypt } from "./sm4";
fire.on("fuck", (a) => {
console.log(a);
/** 与 sm-crypto@0.4.0 + crypto-js@4.2.0(ECB/PKCS#7、密钥见 sm4.js)对同一段 UTF-8 明文应得到相同 Base64。 */
const plain = `{"username":"admin","password":"admin123","code":"40","uuid":"7e9d6dae9c6740c8864d91445b79741d","clientId":"ihF3cJj0Mxf0xYh9rUqrtcJs-f7iQMrF","clientSecret":"-hs4ENSrdMPGhJUxM3E4CogQaGGb_TCReB51KlWxklhJDP-Dkz5JgFTozeshWdqF1UFjKCOiKR--ny53tusqhw"}`;
const validateText = "ryfanf2S7ADSCLhO1rCXLZxHYuPGMXJKJMdUfVzKTKGnsgdccIqABqviVPXsTAnKPWbqDBT0ceITDKfxWH+Aa9n+HPYwcabe1fGR3N0QhV/PI7p9mjLS1N+Sn1EWjrDbs78jPRFWCLRiOAFEmrSKTPaMX3u5zCq+F6Bwkg5S0KmmAwUEYnnL5STqRJpK+d86MRhKDQpNvzgkR4iJYXCBSi5SxtD7dH4uXtdOz/NlJZ7tPMYJ2BD83e2PzjTe2dxJlTx+cDAPfbruJOeivTNayiM3QevGtfbsJHRPy/Ge7dwyqcJSGVkcUiLvzkANBfiZ6YBUwSopYXY2aLKS5fIGWg=="
})
const cipherWasm = encrypt(plain);
const cipherJs = sm4Encrypt(plain);
console.log("[align] wasm === sm-crypto:", cipherWasm === cipherJs);
console.log("[hello-wasm] ciphertext (base64):", cipherWasm);
console.log("[hello-wasm] validateText === cipherWasm:", validateText === cipherWasm);
console.log("[hello-wasm] validateText === cipherJs:", validateText === cipherJs);
setTimeout(() => {
fire.emitSync("fuck", "fuck you")
}, 2000);
const recovered = decrypt(cipherWasm);
console.log("[hello-wasm] round-trip OK:", recovered === plain);

2
packages/example/src/sm4.d.ts

@ -0,0 +1,2 @@
export function encrypt(data: string): string
export function decrypt(data: string): string

59
packages/example/src/sm4.js

@ -0,0 +1,59 @@
import smCrypto from 'sm-crypto'
import CryptoJS from 'crypto-js'
/**
* SM4 密钥Base64 编码
* @constant {string}
*/
const SM4_KEY_BASE64 = 'aceyfpZM9MWztRrzSCi/nA=='
/**
* SM4 密钥Hex 编码
* @constant {string}
*/
const SM4_KEY_HEX = CryptoJS.enc.Base64.parse(SM4_KEY_BASE64).toString(CryptoJS.enc.Hex)
/**
* SM4 加密方法
* @param {string} data - 需要加密的数据
* @returns {string} Base64 编码的加密结果失败返回空字符串
* @example
* const encrypted = encrypt('hello world')
*/
export function encrypt(data) {
if (!data) {
console.error('[SM4] 加密失败:数据不能为空')
return ''
}
try {
const result = smCrypto.sm4.encrypt(data, SM4_KEY_HEX, 0)
return CryptoJS.enc.Base64.stringify(CryptoJS.enc.Hex.parse(result))
} catch (e) {
console.error('[SM4] 加密失败:', e)
return ''
}
}
/**
* SM4 解密方法
* @param {string} data - Base64 编码的加密数据
* @returns {string} 解密后的原始数据失败返回空字符串
* @example
* const decrypted = decrypt(encrypted)
*/
export function decrypt(data) {
if (!data) {
console.error('[SM4] 解密失败:数据不能为空')
return ''
}
try {
const hexData = CryptoJS.enc.Base64.parse(data).toString(CryptoJS.enc.Hex)
const result = smCrypto.sm4.decrypt(hexData, SM4_KEY_HEX)
return result
} catch (e) {
console.error('[SM4] 解密失败:', e)
return ''
}
}

6
packages/example/vite.config.ts

@ -1,5 +1,9 @@
import { defineConfig } from "vite"
import wasm from "vite-plugin-wasm";
export default defineConfig({
base: "./"
base: "./",
plugins: [
wasm()
]
})

6
readme.md

@ -3,6 +3,11 @@
- `bun@1.3.11`
**wasm**
- 安装rust环境
- `cargo install wasm-pack`
## 开发
安装依赖`bun install`,之后需要运行`bun run cli:build`命令安装内部命令行.
@ -12,6 +17,7 @@
1. 开发时不同包之间的的引用最好直接写报名,不要携带路径,统一导出
2. 最好统一包的入口为`src/index.ts`
3. 包的导出记得加上`"development": "./src/index.ts"`
4. wasm包文件夹类似与`*-wasm`
### 子包安装依赖

5
tsconfig.base.json

@ -27,5 +27,8 @@
"packages/*/src/index.ts"
]
}
}
},
"exclude": [
"packages/*-wasm"
]
}
Loading…
Cancel
Save