Skip to content

Cubelrti/sm-crypto-wasm

Repository files navigation

sm-crypto-wasm

Universal ShangMi Cryptography Library in WebAssembly

基于 WebAssembly 的全平台国密加解密库

⚠️ Disclaimer: This project is still under development, and the API may change in the future.

Try with Playground!

Features

  • Decent Performance for Encryption and Decryption by WebAssembly
  • SM2/SM3/SM4 Algorithms and Key Exchange Protocols (not yet implemented)
  • Similar API to sm-crypto and sm-crypto-v2 for development convenience

Supported Runtime

  • Web, Browser (H5)
  • WeChat Mini Program
  • Douyin Mini Program / Toutiao Mini Program
  • Alipay Mini Program (Worker Only, Enterprise Entity Required)

Secure Random Number Generation

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.

Performance

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.

Background

Native WebAssembly is more suggested as it don't have worker count limits and may have better performance.

Platform Support Matrix:

Platform WeChat 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.

Usage

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:

API

If you are using Worker mode, all APIs are async and return Promise.

SM2 Public Key Cryptography

Key Generations

// 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 and Decryption

// 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

Signature and Verification

// 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

SM3 Hash Function

// 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

SM4 Block Cipher

// 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.

License

MIT

Development & Contribution

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)

Reference

This project heavily inspired by the following projects:

More Platform

If you want to support more platforms, please open an issue or PR to discuss.

OpenHarmony

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.

Node.js

NodeJS should be supported by default. You can use wasm-pack to compile the project to Node.js compatible module.

About

Universal Chinese ShangMi Cryptography Library in WebAssembly

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published