Universal ShangMi Cryptography Library in WebAssembly
基于 WebAssembly 的全平台国密加解密库
Try with Playground!
- Decent Performance for Encryption and Decryption by WebAssembly
- SM2/SM3/SM4 Algorithms and Key Exchange Protocols (not yet implemented)
- Similar API to
sm-crypto
andsm-crypto-v2
for development convenience
- Web, Browser (H5)
- WeChat Mini Program
- Douyin Mini Program / Toutiao Mini Program
- Alipay Mini Program (Worker Only, Enterprise Entity Required)
For secure random number generation, we use rand
crate for Rust, and rand_core
for WebAssembly.
Unlike WebAssembly in browser that we can use js-sys
to call crypto.getRandomValues
for secure random number generation, you should always use the following API for populating the RNG seed securely:
sm2.initRNGPool(seed: Uint8Array) // a 32-byte seed
For mini program runtime, only WeChat Mini Program provided wx.getRandomValues
. You can populate the RNG seed by calling sm2.initRNGPool
with the seed generated by wx.getRandomValues
.
// awaiting a promise to block on getting random values from callback api.
await new Promise(rs => {
wx.getRandomValues({
length: 32,
success(res) {
smCrypto.sm2.initRNGPool(new Uint8Array(res.randomValues));
rs()
}
})
})
// therefore, you can use sm2.generateKeyPairHex() safely.
sm2.generateKeyPairHex() // no warning anymore
For other platforms, you may need to provide your own secure random number generation, like get random number from server, or completely avoid using the following algorithms is not deterministic:
- SM2 Key Pair Generation
- SM2 Signature
- SM2 Encryption
It is strongly recommended to use secure random number generation for security. We provide a shim for getRandomValues
for unsupported platforms using Math.random()
to prevent panicking by default, but it is not secure enough for any production use. A warning will be printed if you don't securely seed the random number generator.
Internally we use ChaCha8
cipher for random number generation, which is secure enough for most cases. But for security, the seed should be generated from a secure source and contain enough entropy.
Compared to the most optimized version of sm-crypto-v2
, this project is about 2x faster under some circumstances. The performance may vary on different platforms and devices.
If you are running in some platform that don't have native BigInt support, this project provides 50x-100x faster performance than sm-crypto-v2
using polyfill of BigInt like biginteger.js
or jsbn
.
Native WebAssembly is more suggested as it don't have worker count limits and may have better performance.
Platform Support Matrix:
Platform | Douyin | Alipay | Web | |
---|---|---|---|---|
iOS(Worker) | ✅ (8.0.49) | ✅ (3.0.30) | ✅ (10.6.6) | ✅ |
iOS(Native) | ✅ (8.0.49) | ✅ (3.0.30) | ❌ | ✅ |
Android(Worker) | ✅ (8.0.49) | ✅ (30.5.0) | ✅ (10.6.10) | ✅ |
Android(Native) | ✅ (8.0.49) | ✅ (30.5.0) | ❌ | ✅ |
Notes:
- Alipay only support WebAssembly inside Worker.
- Alipay worker message has very bad serialization support, only flat object with array/string/number are supported. Nested array of object will be dropped or converted into string unexpectedly.
- WeChat need manual removal of
instantiateStreaming
for Worker on Android. - Douyin need custom shim for Crypto API and TextEncoder/TextDecoder.
- WeChat need custom shim for TextEncoder/TextDecoder for Native WebAssembly.
- WeChat and Douyin Android can only pass ArrayBuffer back and forth between JS and Worker.
Since we are using WebAssembly which is not supported in many platform with NPM package, you may need to run the following cli command to install the package:
# for weapp/WeChat Mini Program
npx degit github:Cubelrti/sm-crypto-wasm/templates/weapp/sm-crypto sm-crypto
# for alipay
npx degit github:Cubelrti/sm-crypto-wasm/templates/alipay/sm-crypto sm-crypto
# for Tiktok/Douyin
npx degit github:Cubelrti/sm-crypto-wasm/templates/tt/sm-crypto sm-crypto
Additionally, alipay need to set workers
path in app.json
:
{
"workers": [
"sm-crypto/workers/sm-crypto.js"
],
}
Then you can import the package in your project. You need to set resolveAlias
in app.json
if you want to use absolute path like this:
{
"resolveAlias": {
"@/*": "/*"
},
}
// pages/index/index.js
import smCrypto from '@/sm-crypto/index'
smCrypto.initSMCrypto().then(console.log)
or, view templates
for specific platform to start.
Since the project is still under development, you may need to build the project by yourself.
Alternatively, you can see the code snippet for several DevTools:
If you are using Worker mode, all APIs are async and return Promise.
// seeding RNG, it is strongly recommended by using a seed has enough entropy
sm2.initRNGPool(new Uint8Array(32))
// generating key pair, generating 65-byte long public key starts with `04` in hex
let keypair = sm2.generateKeyPairHex() // => { privateKey: string, publicKey: string }
// compressing public key hex into 33-byte long public key starts with `02` or `03` in hex
let compressedPublicKey = sm2.compressPublicKeyHex(keypair.publicKey) // => string
// encryption, using public key and return hex encoded string
let ciphertext = sm2.encrypt(new Uint8Array([
0xde, 0xad, 0xbe, 0xef
]), compressed, { output: 'string'}) // => string
// encryption, using public key and return Uint8Array, encoded with ASN.1 and C1C2C3 mode
let ciphertext = sm2.encrypt(new Uint8Array([
0xde, 0xad, 0xbe, 0xef
]), compressed, { output: 'array', asn1: true, cipherMode: 1}) // => Uint8Array
// signing, using private key and return hex encoded string
let signature = sm2.doSignature(new Uint8Array([
0xde, 0xad, 0xbe, 0xef
]), keypair.privateKey, { der: true, hash: true }) // => string
// verifying, using public key and return boolean
let verified = sm2.doVerifySignature(new Uint8Array([
0xde, 0xad, 0xbe, 0xef
]), signature, keypair.publicKey, { der: true, hash: true }) // => boolean
// hashing, return hex encoded string
let hash = sm3(new Uint8Array([
0xde, 0xad, 0xbe, 0xef
])) // => string
// hmac, return hex encoded string
let hmac = sm3(new Uint8Array([
0xde, 0xad, 0xbe, 0xef
]), {
key: new Uint8Array([0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef]) // hex encoded string or Uint8Array
}) // => string
// encrypting, return hex encoded string
let ciphertext = sm4.encrypt(new Uint8Array([
0xde, 0xad, 0xbe, 0xef
]), key, { output: 'string', mode: 'cbc', iv: 'fedcba98765432100123456789abcdef' }) // => string
// decrypting, return hex encoded string
let plaintext = sm4.decrypt(new Uint8Array([
0xde, 0xad, 0xbe, 0xef
]), key, { output: 'string', mode: 'cbc', iv: 'fedcba98765432100123456789abcdef' }) // => string
// gcm (experimental), need to provide aad and 12-byte iv
// iv longer than 12 bytes are hashed and used as counter part
let gcm = sm4.encrypt(new Uint8Array([
0xde, 0xad, 0xbe, 0xef
]), hexToBytes("0123456789ABCDEFFEDCBA9876543210"), { output: 'string', mode: 'gcm', iv: '00001234567800000000ABCD', aad: 'FEEDFACEDEADBEEFFEEDFACEDEADBEEFABADDAD2' }) // => string
Note that input should always be Uint8Array, and output can be either Uint8Array or hex encoded string.
You should encode the input by yourself if it is UTF-8 string by new TextEncoder().encode
. You can refer to js/shim-encoding.js
if you don't have TextEncoder/TextDecoder in your environment.
MIT
For usability, we use some nightly features, so you may need to install nightly Rust:
rustup install nightly # for reference, we use rustc 1.81.0-nightly (24d2ac0b5 2024-07-15)
rustup default nightly-2024-02-28-aarch64-apple-darwin
rustc --version
# rustc 1.81.0-nightly (24d2ac0b5 2024-07-15)
This project heavily inspired by the following projects:
If you want to support more platforms, please open an issue or PR to discuss.
For OpenHarmony, since there is no WebAssembly support, you can compile the Rust core of this repo (under crypto
folder), and compile it to cdylib for C/C++ usage. And HarmonyOS provides a Crypto Framework for SM2/SM3/SM4, you can use it directly.
You can refer to Official Rust Platform support or ohos-rs for more information.
NodeJS should be supported by default. You can use wasm-pack
to compile the project to Node.js compatible module.