文章目录
- [1 准备环境](#1 准备环境)
-
- [1.1 更新环境](#1.1 更新环境)
- [1.2 安装基础开发包](#1.2 安装基础开发包)
- [1.3 安装 Emscripten](#1.3 安装 Emscripten)
- [1.4 gmssl 源码](#1.4 gmssl 源码)
- [2 编译 wasm](#2 编译 wasm)
- [3 给浏览器使用](#3 给浏览器使用)
-
- [3.1 写导出层](#3.1 写导出层)
- [3.2 生成浏览器可用的 JS/WASM](#3.2 生成浏览器可用的 JS/WASM)
- [3.3 写测试 html](#3.3 写测试 html)
- [3.4 运行测试](#3.4 运行测试)

1 准备环境
本次编译在 ubuntu24.04 系统上进行
1.1 更新环境
sudo apt update
sudo apt upgrade -y
1.2 安装基础开发包
sudo apt install -y \
build-essential \
clang \
llvm \
lld \
cmake \
make \
git \
python3 \
python3-pip \
ninja-build \
pkg-config
wasm 测试通常依赖 JS runtime
sudo apt install -y nodejs npm
1.3 安装 Emscripten
下载emsdk
git clone https://github.com/emscripten-core/emsdk.git
cd emsdk
安装最新版
./emsdk install latest
激活环境
./emsdk activate latest
导入环境变量
source ./emsdk_env.sh
验证
emcc -v
看到类似:
emcc (Emscripten gcc/clang-like replacement)
说明成功。
1.4 gmssl 源码
准备 gmssl 源码。
方式一:可以从 connector-tiny 项目中复制 gmssl 目录,其中即是 gmssl 源码,版本为 v3.1.1
方式二:从 GmSSL githbu 仓库中下载源码,版本为 v3.1.1
2 编译 wasm
进入 gmssl 目录
cd gmssl
创建 wasm 构建目录
mkdir build_wasm && cd build_wasm
使用 emcmake 配置
emcmake cmake .. -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF -DBUILD_TESTING=OFF
编译
emmake make -j$(nproc)
生成的静态库路径是:
text
gmssl/build_wasm/bin/libgmssl.a
3 给浏览器使用
不要直接用 gmssl/build_wasm/bin/ 目录下的这些 test.js / demo_*.js。
它们主要是测试程序,不适合作为稳定 API。
需要自己写导出层,然后使用 emcc 编译成 wasm 和 js 文件
3.1 写导出层
在 gmssl 根目录新建
mkdir -p wasm_api dist
vim wasm_api/gmssl_wasm.c
写入:
c
/*
* Browser-oriented GmSSL WASM export layer.
*
* Return convention:
* - data functions return 0 on success and -1 on parameter/library errors
* - verify functions return 1 for valid, 0 for invalid, and -1 for errors
*/
#include <stdint.h>
#include <stddef.h>
#include <string.h>
#ifdef __EMSCRIPTEN__
#include <emscripten/emscripten.h>
#else
#define EMSCRIPTEN_KEEPALIVE
#endif
#include <gmssl/base64.h>
#include <gmssl/digest.h>
#include <gmssl/hex.h>
#include <gmssl/hkdf.h>
#include <gmssl/pbkdf2.h>
#include <gmssl/rand.h>
#include <gmssl/sm2.h>
#include <gmssl/sm3.h>
#include <gmssl/sm4.h>
#include <gmssl/version.h>
#include <gmssl/zuc.h>
#define GMSSL_WASM_OK 0
#define GMSSL_WASM_ERR (-1)
static const char GMSSL_WASM_HEX[] = "0123456789abcdef";
static void gmssl_wasm_bytes_to_hex(const uint8_t *in, size_t inlen, char *out)
{
size_t i;
for (i = 0; i < inlen; i++) {
out[i * 2] = GMSSL_WASM_HEX[in[i] >> 4];
out[i * 2 + 1] = GMSSL_WASM_HEX[in[i] & 0x0f];
}
out[inlen * 2] = '\0';
}
static int gmssl_wasm_set_public_key(SM2_KEY *key, const uint8_t *public_key, size_t public_key_len)
{
SM2_POINT point;
if (!key || !public_key) {
return GMSSL_WASM_ERR;
}
if (sm2_point_from_octets(&point, public_key, public_key_len) != 1) {
return GMSSL_WASM_ERR;
}
if (sm2_key_set_public_key(key, &point) != 1) {
return GMSSL_WASM_ERR;
}
return GMSSL_WASM_OK;
}
EMSCRIPTEN_KEEPALIVE
int gmssl_wasm_version_num(void)
{
return gmssl_version_num();
}
EMSCRIPTEN_KEEPALIVE
const char *gmssl_wasm_version_str(void)
{
return gmssl_version_str();
}
EMSCRIPTEN_KEEPALIVE
int gmssl_rand_bytes(uint8_t *out, uint32_t out_len)
{
if (!out) {
return GMSSL_WASM_ERR;
}
return rand_bytes(out, out_len) == 1 ? GMSSL_WASM_OK : GMSSL_WASM_ERR;
}
EMSCRIPTEN_KEEPALIVE
int gmssl_hex_to_bytes(const char *hex, uint32_t hex_len, uint8_t *out, uint32_t out_cap, uint32_t *out_len)
{
size_t len = out_cap;
if (!hex || !out || !out_len) {
return GMSSL_WASM_ERR;
}
if (hex_to_bytes(hex, hex_len, out, &len) != 1 || len > out_cap) {
return GMSSL_WASM_ERR;
}
*out_len = (uint32_t)len;
return GMSSL_WASM_OK;
}
EMSCRIPTEN_KEEPALIVE
int gmssl_bytes_to_hex(const uint8_t *in, uint32_t in_len, char *out_hex, uint32_t out_cap)
{
if (!in || !out_hex || out_cap < in_len * 2u + 1u) {
return GMSSL_WASM_ERR;
}
gmssl_wasm_bytes_to_hex(in, in_len, out_hex);
return GMSSL_WASM_OK;
}
EMSCRIPTEN_KEEPALIVE
int gmssl_base64_encode(const uint8_t *in, uint32_t in_len, uint8_t *out, uint32_t out_cap, uint32_t *out_len)
{
BASE64_CTX ctx;
int len = 0;
int fin_len = 0;
if (!in || !out || !out_len || in_len > INT32_MAX) {
return GMSSL_WASM_ERR;
}
if (out_cap < (uint32_t)BASE64_ENCODE_LENGTH(in_len)) {
return GMSSL_WASM_ERR;
}
base64_encode_init(&ctx);
if (base64_encode_update(&ctx, in, (int)in_len, out, &len) != 1) {
return GMSSL_WASM_ERR;
}
base64_encode_finish(&ctx, out + len, &fin_len);
*out_len = (uint32_t)(len + fin_len);
return GMSSL_WASM_OK;
}
EMSCRIPTEN_KEEPALIVE
int gmssl_base64_decode(const uint8_t *in, uint32_t in_len, uint8_t *out, uint32_t out_cap, uint32_t *out_len)
{
BASE64_CTX ctx;
int len = 0;
int fin_len = 0;
if (!in || !out || !out_len || in_len > INT32_MAX) {
return GMSSL_WASM_ERR;
}
if (out_cap < (uint32_t)BASE64_DECODE_LENGTH(in_len)) {
return GMSSL_WASM_ERR;
}
base64_decode_init(&ctx);
if (base64_decode_update(&ctx, in, (int)in_len, out, &len) != 1) {
return GMSSL_WASM_ERR;
}
if (base64_decode_finish(&ctx, out + len, &fin_len) != 1) {
return GMSSL_WASM_ERR;
}
*out_len = (uint32_t)(len + fin_len);
return GMSSL_WASM_OK;
}
EMSCRIPTEN_KEEPALIVE
int gmssl_sm3_digest(const uint8_t *input, uint32_t input_len, uint8_t *out32)
{
if (!input || !out32) {
return GMSSL_WASM_ERR;
}
sm3_digest(input, input_len, out32);
return GMSSL_WASM_OK;
}
EMSCRIPTEN_KEEPALIVE
int gmssl_sm3_hex(const uint8_t *input, uint32_t input_len, char *out_hex65)
{
uint8_t digest[SM3_DIGEST_SIZE];
if (!input || !out_hex65) {
return GMSSL_WASM_ERR;
}
sm3_digest(input, input_len, digest);
gmssl_wasm_bytes_to_hex(digest, sizeof(digest), out_hex65);
return GMSSL_WASM_OK;
}
EMSCRIPTEN_KEEPALIVE
int gmssl_sm3_hmac(const uint8_t *key, uint32_t key_len, const uint8_t *input, uint32_t input_len, uint8_t *out32)
{
if (!key || !input || !out32) {
return GMSSL_WASM_ERR;
}
sm3_hmac(key, key_len, input, input_len, out32);
return GMSSL_WASM_OK;
}
EMSCRIPTEN_KEEPALIVE
int gmssl_sm3_hmac_hex(const uint8_t *key, uint32_t key_len, const uint8_t *input, uint32_t input_len, char *out_hex65)
{
uint8_t mac[SM3_HMAC_SIZE];
if (!key || !input || !out_hex65) {
return GMSSL_WASM_ERR;
}
sm3_hmac(key, key_len, input, input_len, mac);
gmssl_wasm_bytes_to_hex(mac, sizeof(mac), out_hex65);
return GMSSL_WASM_OK;
}
EMSCRIPTEN_KEEPALIVE
int gmssl_sm3_kdf(const uint8_t *input, uint32_t input_len, uint8_t *out, uint32_t out_len)
{
SM3_KDF_CTX ctx;
if (!input || !out) {
return GMSSL_WASM_ERR;
}
sm3_kdf_init(&ctx, out_len);
sm3_kdf_update(&ctx, input, input_len);
sm3_kdf_finish(&ctx, out);
return GMSSL_WASM_OK;
}
EMSCRIPTEN_KEEPALIVE
int gmssl_sm3_hkdf_extract(const uint8_t *salt, uint32_t salt_len, const uint8_t *ikm, uint32_t ikm_len,
uint8_t *prk, uint32_t *prk_len)
{
size_t len = SM3_DIGEST_SIZE;
if (!ikm || !prk || !prk_len) {
return GMSSL_WASM_ERR;
}
if (hkdf_extract(DIGEST_sm3(), salt, salt_len, ikm, ikm_len, prk, &len) != 1) {
return GMSSL_WASM_ERR;
}
*prk_len = (uint32_t)len;
return GMSSL_WASM_OK;
}
EMSCRIPTEN_KEEPALIVE
int gmssl_sm3_hkdf_expand(const uint8_t *prk, uint32_t prk_len, const uint8_t *info, uint32_t info_len,
uint8_t *okm, uint32_t okm_len)
{
if (!prk || !okm) {
return GMSSL_WASM_ERR;
}
return hkdf_expand(DIGEST_sm3(), prk, prk_len, info, info_len, okm_len, okm) == 1
? GMSSL_WASM_OK : GMSSL_WASM_ERR;
}
EMSCRIPTEN_KEEPALIVE
int gmssl_pbkdf2_hmac_sm3(const char *pass, uint32_t pass_len, const uint8_t *salt, uint32_t salt_len,
uint32_t iter, uint8_t *out, uint32_t out_len)
{
if (!pass || !salt || !out) {
return GMSSL_WASM_ERR;
}
return pbkdf2_hmac_sm3_genkey(pass, pass_len, salt, salt_len, iter, out_len, out) == 1
? GMSSL_WASM_OK : GMSSL_WASM_ERR;
}
EMSCRIPTEN_KEEPALIVE
int gmssl_sm4_encrypt_block(const uint8_t *key16, const uint8_t *in16, uint8_t *out16)
{
SM4_KEY key;
if (!key16 || !in16 || !out16) {
return GMSSL_WASM_ERR;
}
sm4_set_encrypt_key(&key, key16);
sm4_encrypt(&key, in16, out16);
return GMSSL_WASM_OK;
}
EMSCRIPTEN_KEEPALIVE
int gmssl_sm4_decrypt_block(const uint8_t *key16, const uint8_t *in16, uint8_t *out16)
{
SM4_KEY key;
if (!key16 || !in16 || !out16) {
return GMSSL_WASM_ERR;
}
sm4_set_decrypt_key(&key, key16);
sm4_decrypt(&key, in16, out16);
return GMSSL_WASM_OK;
}
EMSCRIPTEN_KEEPALIVE
int gmssl_sm4_cbc_padding_encrypt(const uint8_t *key16, const uint8_t *iv16,
const uint8_t *in, uint32_t in_len, uint8_t *out, uint32_t out_cap, uint32_t *out_len)
{
SM4_KEY key;
size_t len = out_cap;
if (!key16 || !iv16 || !in || !out || !out_len) {
return GMSSL_WASM_ERR;
}
sm4_set_encrypt_key(&key, key16);
if (sm4_cbc_padding_encrypt(&key, iv16, in, in_len, out, &len) != 1 || len > out_cap) {
return GMSSL_WASM_ERR;
}
*out_len = (uint32_t)len;
return GMSSL_WASM_OK;
}
EMSCRIPTEN_KEEPALIVE
int gmssl_sm4_cbc_padding_decrypt(const uint8_t *key16, const uint8_t *iv16,
const uint8_t *in, uint32_t in_len, uint8_t *out, uint32_t out_cap, uint32_t *out_len)
{
SM4_KEY key;
size_t len = out_cap;
if (!key16 || !iv16 || !in || !out || !out_len) {
return GMSSL_WASM_ERR;
}
sm4_set_decrypt_key(&key, key16);
if (sm4_cbc_padding_decrypt(&key, iv16, in, in_len, out, &len) != 1 || len > out_cap) {
return GMSSL_WASM_ERR;
}
*out_len = (uint32_t)len;
return GMSSL_WASM_OK;
}
EMSCRIPTEN_KEEPALIVE
int gmssl_sm4_ctr_encrypt(const uint8_t *key16, const uint8_t *ctr16,
const uint8_t *in, uint32_t in_len, uint8_t *out)
{
SM4_KEY key;
uint8_t ctr[SM4_BLOCK_SIZE];
if (!key16 || !ctr16 || !in || !out) {
return GMSSL_WASM_ERR;
}
memcpy(ctr, ctr16, sizeof(ctr));
sm4_set_encrypt_key(&key, key16);
sm4_ctr_encrypt(&key, ctr, in, in_len, out);
return GMSSL_WASM_OK;
}
EMSCRIPTEN_KEEPALIVE
int gmssl_sm4_ctr_decrypt(const uint8_t *key16, const uint8_t *ctr16,
const uint8_t *in, uint32_t in_len, uint8_t *out)
{
return gmssl_sm4_ctr_encrypt(key16, ctr16, in, in_len, out);
}
EMSCRIPTEN_KEEPALIVE
int gmssl_sm4_gcm_encrypt(const uint8_t *key16, const uint8_t *iv, uint32_t iv_len,
const uint8_t *aad, uint32_t aad_len, const uint8_t *in, uint32_t in_len,
uint8_t *out, uint8_t *tag, uint32_t tag_len)
{
SM4_KEY key;
if (!key16 || !iv || !in || !out || !tag) {
return GMSSL_WASM_ERR;
}
sm4_set_encrypt_key(&key, key16);
return sm4_gcm_encrypt(&key, iv, iv_len, aad, aad_len, in, in_len, out, tag_len, tag) == 1
? GMSSL_WASM_OK : GMSSL_WASM_ERR;
}
EMSCRIPTEN_KEEPALIVE
int gmssl_sm4_gcm_decrypt(const uint8_t *key16, const uint8_t *iv, uint32_t iv_len,
const uint8_t *aad, uint32_t aad_len, const uint8_t *in, uint32_t in_len,
const uint8_t *tag, uint32_t tag_len, uint8_t *out)
{
SM4_KEY key;
if (!key16 || !iv || !in || !tag || !out) {
return GMSSL_WASM_ERR;
}
sm4_set_encrypt_key(&key, key16);
return sm4_gcm_decrypt(&key, iv, iv_len, aad, aad_len, in, in_len, tag, tag_len, out) == 1
? GMSSL_WASM_OK : GMSSL_WASM_ERR;
}
EMSCRIPTEN_KEEPALIVE
int gmssl_sm2_generate_key(uint8_t *private_key32, uint8_t *public_key65)
{
SM2_KEY key;
if (!private_key32 || !public_key65) {
return GMSSL_WASM_ERR;
}
if (sm2_key_generate(&key) != 1) {
return GMSSL_WASM_ERR;
}
memcpy(private_key32, key.private_key, 32);
sm2_point_to_uncompressed_octets(&key.public_key, public_key65);
return GMSSL_WASM_OK;
}
EMSCRIPTEN_KEEPALIVE
int gmssl_sm2_public_key_from_private(const uint8_t *private_key32, uint8_t *public_key65)
{
SM2_KEY key;
if (!private_key32 || !public_key65) {
return GMSSL_WASM_ERR;
}
if (sm2_key_set_private_key(&key, private_key32) != 1) {
return GMSSL_WASM_ERR;
}
sm2_point_to_uncompressed_octets(&key.public_key, public_key65);
return GMSSL_WASM_OK;
}
EMSCRIPTEN_KEEPALIVE
int gmssl_sm2_sign_digest(const uint8_t *private_key32, const uint8_t *digest32,
uint8_t *sig, uint32_t sig_cap, uint32_t *sig_len)
{
SM2_KEY key;
size_t len = sig_cap;
if (!private_key32 || !digest32 || !sig || !sig_len) {
return GMSSL_WASM_ERR;
}
if (sm2_key_set_private_key(&key, private_key32) != 1) {
return GMSSL_WASM_ERR;
}
if (sm2_sign(&key, digest32, sig, &len) != 1 || len > sig_cap) {
return GMSSL_WASM_ERR;
}
*sig_len = (uint32_t)len;
return GMSSL_WASM_OK;
}
EMSCRIPTEN_KEEPALIVE
int gmssl_sm2_verify_digest(const uint8_t *public_key65, uint32_t public_key_len,
const uint8_t *digest32, const uint8_t *sig, uint32_t sig_len)
{
SM2_KEY key;
int ret;
if (!public_key65 || !digest32 || !sig) {
return GMSSL_WASM_ERR;
}
if (gmssl_wasm_set_public_key(&key, public_key65, public_key_len) != GMSSL_WASM_OK) {
return GMSSL_WASM_ERR;
}
ret = sm2_verify(&key, digest32, sig, sig_len);
return ret == 1 ? 1 : 0;
}
EMSCRIPTEN_KEEPALIVE
int gmssl_sm2_sign(const uint8_t *private_key32, const char *id, uint32_t id_len,
const uint8_t *input, uint32_t input_len, uint8_t *sig, uint32_t sig_cap, uint32_t *sig_len)
{
SM2_KEY key;
SM2_SIGN_CTX ctx;
size_t len = sig_cap;
if (!private_key32 || !input || !sig || !sig_len) {
return GMSSL_WASM_ERR;
}
if (sm2_key_set_private_key(&key, private_key32) != 1
|| sm2_sign_init(&ctx, &key, id, id_len) != 1
|| sm2_sign_update(&ctx, input, input_len) != 1
|| sm2_sign_finish(&ctx, sig, &len) != 1
|| len > sig_cap) {
return GMSSL_WASM_ERR;
}
*sig_len = (uint32_t)len;
return GMSSL_WASM_OK;
}
EMSCRIPTEN_KEEPALIVE
int gmssl_sm2_verify(const uint8_t *public_key65, uint32_t public_key_len, const char *id, uint32_t id_len,
const uint8_t *input, uint32_t input_len, const uint8_t *sig, uint32_t sig_len)
{
SM2_KEY key;
SM2_SIGN_CTX ctx;
if (!public_key65 || !input || !sig) {
return GMSSL_WASM_ERR;
}
if (gmssl_wasm_set_public_key(&key, public_key65, public_key_len) != GMSSL_WASM_OK
|| sm2_verify_init(&ctx, &key, id, id_len) != 1
|| sm2_verify_update(&ctx, input, input_len) != 1) {
return GMSSL_WASM_ERR;
}
return sm2_verify_finish(&ctx, sig, sig_len) == 1 ? 1 : 0;
}
EMSCRIPTEN_KEEPALIVE
int gmssl_sm2_encrypt(const uint8_t *public_key65, uint32_t public_key_len,
const uint8_t *in, uint32_t in_len, uint8_t *out, uint32_t out_cap, uint32_t *out_len)
{
SM2_KEY key;
size_t len = out_cap;
if (!public_key65 || !in || !out || !out_len) {
return GMSSL_WASM_ERR;
}
if (gmssl_wasm_set_public_key(&key, public_key65, public_key_len) != GMSSL_WASM_OK) {
return GMSSL_WASM_ERR;
}
if (sm2_encrypt(&key, in, in_len, out, &len) != 1 || len > out_cap) {
return GMSSL_WASM_ERR;
}
*out_len = (uint32_t)len;
return GMSSL_WASM_OK;
}
EMSCRIPTEN_KEEPALIVE
int gmssl_sm2_decrypt(const uint8_t *private_key32,
const uint8_t *in, uint32_t in_len, uint8_t *out, uint32_t out_cap, uint32_t *out_len)
{
SM2_KEY key;
size_t len = out_cap;
if (!private_key32 || !in || !out || !out_len) {
return GMSSL_WASM_ERR;
}
if (sm2_key_set_private_key(&key, private_key32) != 1) {
return GMSSL_WASM_ERR;
}
if (sm2_decrypt(&key, in, in_len, out, &len) != 1 || len > out_cap) {
return GMSSL_WASM_ERR;
}
*out_len = (uint32_t)len;
return GMSSL_WASM_OK;
}
EMSCRIPTEN_KEEPALIVE
int gmssl_sm2_ecdh(const uint8_t *private_key32, const uint8_t *peer_public_key65, uint32_t peer_public_key_len,
uint8_t *out_public_point65)
{
SM2_KEY key;
SM2_POINT out;
if (!private_key32 || !peer_public_key65 || !out_public_point65) {
return GMSSL_WASM_ERR;
}
if (sm2_key_set_private_key(&key, private_key32) != 1) {
return GMSSL_WASM_ERR;
}
if (sm2_ecdh(&key, peer_public_key65, peer_public_key_len, &out) != 1) {
return GMSSL_WASM_ERR;
}
sm2_point_to_uncompressed_octets(&out, out_public_point65);
return GMSSL_WASM_OK;
}
EMSCRIPTEN_KEEPALIVE
int gmssl_zuc_encrypt(const uint8_t *key16, const uint8_t *iv16, const uint8_t *in, uint32_t in_len, uint8_t *out)
{
ZUC_STATE state;
if (!key16 || !iv16 || !in || !out) {
return GMSSL_WASM_ERR;
}
zuc_init(&state, key16, iv16);
zuc_encrypt(&state, in, in_len, out);
return GMSSL_WASM_OK;
}
EMSCRIPTEN_KEEPALIVE
int gmssl_zuc_mac(const uint8_t *key16, const uint8_t *iv16, const uint8_t *in, uint32_t in_len, uint8_t *mac4)
{
ZUC_MAC_CTX ctx;
if (!key16 || !iv16 || !in || !mac4) {
return GMSSL_WASM_ERR;
}
zuc_mac_init(&ctx, key16, iv16);
zuc_mac_update(&ctx, in, in_len);
zuc_mac_finish(&ctx, NULL, 0, mac4);
return GMSSL_WASM_OK;
}
EMSCRIPTEN_KEEPALIVE
int gmssl_zuc256_encrypt(const uint8_t *key32, const uint8_t *iv23, const uint8_t *in, uint32_t in_len, uint8_t *out)
{
ZUC256_STATE state;
if (!key32 || !iv23 || !in || !out) {
return GMSSL_WASM_ERR;
}
zuc256_init(&state, key32, iv23);
zuc_encrypt(&state, in, in_len, out);
return GMSSL_WASM_OK;
}
EMSCRIPTEN_KEEPALIVE
int gmssl_zuc256_mac(const uint8_t *key32, const uint8_t *iv23, uint32_t mac_bits,
const uint8_t *in, uint32_t in_len, uint8_t *mac)
{
ZUC256_MAC_CTX ctx;
if (!key32 || !iv23 || !in || !mac) {
return GMSSL_WASM_ERR;
}
if (mac_bits != 32 && mac_bits != 64 && mac_bits != 128) {
return GMSSL_WASM_ERR;
}
zuc256_mac_init(&ctx, key32, iv23, (int)mac_bits);
zuc256_mac_update(&ctx, in, in_len);
zuc256_mac_finish(&ctx, NULL, 0, mac);
return GMSSL_WASM_OK;
}
3.2 生成浏览器可用的 JS/WASM
在 gmssl 根目录执行
emcc wasm_api/gmssl_wasm.c \
-I./include \
./build_wasm/bin/libgmssl.a \
-O3 \
-s WASM=1 \
-s MODULARIZE=1 \
-s EXPORT_NAME=createGmSSL \
-s ENVIRONMENT=web,webview \
-s ALLOW_MEMORY_GROWTH=1 \
-s EXPORTED_FUNCTIONS='["_malloc","_free","_gmssl_wasm_version_num","_gmssl_wasm_version_str","_gmssl_rand_bytes","_gmssl_hex_to_bytes","_gmssl_bytes_to_hex","_gmssl_base64_encode","_gmssl_base64_decode","_gmssl_sm3_digest","_gmssl_sm3_hex","_gmssl_sm3_hmac","_gmssl_sm3_hmac_hex","_gmssl_sm3_kdf","_gmssl_sm3_hkdf_extract","_gmssl_sm3_hkdf_expand","_gmssl_pbkdf2_hmac_sm3","_gmssl_sm4_encrypt_block","_gmssl_sm4_decrypt_block","_gmssl_sm4_cbc_padding_encrypt","_gmssl_sm4_cbc_padding_decrypt","_gmssl_sm4_ctr_encrypt","_gmssl_sm4_ctr_decrypt","_gmssl_sm4_gcm_encrypt","_gmssl_sm4_gcm_decrypt","_gmssl_sm2_generate_key","_gmssl_sm2_public_key_from_private","_gmssl_sm2_sign_digest","_gmssl_sm2_verify_digest","_gmssl_sm2_sign","_gmssl_sm2_verify","_gmssl_sm2_encrypt","_gmssl_sm2_decrypt","_gmssl_sm2_ecdh","_gmssl_zuc_encrypt","_gmssl_zuc_mac","_gmssl_zuc256_encrypt","_gmssl_zuc256_mac"]' \
-s EXPORTED_RUNTIME_METHODS='["HEAPU8","HEAPU32","UTF8ToString"]' \
-o dist/gmssl.js
生成结果:
text
dist/gmssl.js
dist/gmssl.wasm
3.3 写测试 html
创建 dist/index.html
写入测试 html 代码
html
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>GmSSL WASM Browser Test</title>
<style>
:root {
color-scheme: light dark;
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
line-height: 1.45;
}
body {
margin: 0;
background: Canvas;
color: CanvasText;
}
main {
width: min(1120px, calc(100vw - 32px));
margin: 0 auto;
padding: 28px 0 40px;
}
header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
margin-bottom: 20px;
}
h1 {
margin: 0;
font-size: 24px;
font-weight: 650;
}
button {
border: 1px solid color-mix(in srgb, CanvasText 24%, transparent);
border-radius: 6px;
background: color-mix(in srgb, Canvas 88%, CanvasText);
color: CanvasText;
padding: 9px 14px;
font: inherit;
cursor: pointer;
}
button:disabled {
cursor: wait;
opacity: 0.62;
}
#summary {
margin: 0 0 16px;
font-size: 14px;
color: color-mix(in srgb, CanvasText 72%, transparent);
}
table {
width: 100%;
border-collapse: collapse;
border: 1px solid color-mix(in srgb, CanvasText 18%, transparent);
border-radius: 8px;
overflow: hidden;
}
th, td {
padding: 10px 12px;
border-bottom: 1px solid color-mix(in srgb, CanvasText 14%, transparent);
text-align: left;
vertical-align: top;
font-size: 14px;
}
th {
background: color-mix(in srgb, CanvasText 8%, transparent);
font-weight: 650;
}
tr:last-child td {
border-bottom: 0;
}
.ok {
color: #12805c;
font-weight: 650;
}
.fail {
color: #b42318;
font-weight: 650;
}
code {
word-break: break-all;
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
font-size: 12px;
}
</style>
</head>
<body>
<main>
<header>
<h1>GmSSL WASM Browser Test</h1>
<button id="run">运行测试</button>
</header>
<p id="summary">等待加载 <code>gmssl.js</code> 和 <code>gmssl.wasm</code>。</p>
<table>
<thead>
<tr>
<th style="width: 180px;">模块</th>
<th style="width: 90px;">状态</th>
<th>结果</th>
</tr>
</thead>
<tbody id="results"></tbody>
</table>
</main>
<script>
const encoder = new TextEncoder();
const decoder = new TextDecoder();
const results = document.getElementById("results");
const summary = document.getElementById("summary");
const runButton = document.getElementById("run");
function loadScript(src) {
return new Promise((resolve, reject) => {
const script = document.createElement("script");
script.src = src;
script.onload = resolve;
script.onerror = () => reject(new Error(`无法加载 ${src},请先按文档生成 gmssl.js/gmssl.wasm`));
document.head.appendChild(script);
});
}
function addResult(name, ok, detail) {
const tr = document.createElement("tr");
tr.innerHTML = `<td>${name}</td><td class="${ok ? "ok" : "fail"}">${ok ? "PASS" : "FAIL"}</td><td><code></code></td>`;
tr.querySelector("code").textContent = detail;
results.appendChild(tr);
}
function toHex(bytes) {
return Array.from(bytes, b => b.toString(16).padStart(2, "0")).join("");
}
function fromHex(hex) {
const out = new Uint8Array(hex.length / 2);
for (let i = 0; i < out.length; i++) out[i] = parseInt(hex.slice(i * 2, i * 2 + 2), 16);
return out;
}
function wasmString(api, ptr) {
return api.UTF8ToString(ptr);
}
function makeClient(api) {
function alloc(bytes) {
const ptr = api._malloc(bytes.length || 1);
api.HEAPU8.set(bytes, ptr);
return ptr;
}
function withOut(len, fn) {
const ptr = api._malloc(len || 1);
try {
return fn(ptr);
} finally {
api._free(ptr);
}
}
function withBytes(bytes, fn) {
const ptr = alloc(bytes);
try {
return fn(ptr);
} finally {
api._free(ptr);
}
}
function read(ptr, len) {
return new Uint8Array(api.HEAPU8.subarray(ptr, ptr + len));
}
function lenPtr() {
const ptr = api._malloc(4);
api.HEAPU32[ptr >> 2] = 0;
return ptr;
}
function expectZero(ret, name) {
if (ret !== 0) throw new Error(`${name} returned ${ret}`);
}
function sm3Hex(text) {
const data = encoder.encode(text);
return withBytes(data, inPtr => withOut(65, outPtr => {
expectZero(api._gmssl_sm3_hex(inPtr, data.length, outPtr), "gmssl_sm3_hex");
return wasmString(api, outPtr);
}));
}
function bytesToHex(bytes) {
return withBytes(bytes, inPtr => withOut(bytes.length * 2 + 1, outPtr => {
expectZero(api._gmssl_bytes_to_hex(inPtr, bytes.length, outPtr, bytes.length * 2 + 1), "gmssl_bytes_to_hex");
return wasmString(api, outPtr);
}));
}
function sm3HmacHex(keyText, text) {
const key = encoder.encode(keyText);
const data = encoder.encode(text);
return withBytes(key, keyPtr => withBytes(data, inPtr => withOut(65, outPtr => {
expectZero(api._gmssl_sm3_hmac_hex(keyPtr, key.length, inPtr, data.length, outPtr), "gmssl_sm3_hmac_hex");
return wasmString(api, outPtr);
})));
}
function sm4Block(key, input, decrypt = false) {
return withBytes(key, keyPtr => withBytes(input, inPtr => withOut(16, outPtr => {
expectZero((decrypt ? api._gmssl_sm4_decrypt_block : api._gmssl_sm4_encrypt_block)(keyPtr, inPtr, outPtr), "gmssl_sm4_block");
return read(outPtr, 16);
})));
}
function sm4CbcRoundtrip(key, iv, plain) {
return withBytes(key, keyPtr => withBytes(iv, ivPtr => withBytes(plain, inPtr => {
const encCap = plain.length + 16;
const encLenPtr = lenPtr();
const decLenPtr = lenPtr();
const encPtr = api._malloc(encCap);
const decPtr = api._malloc(encCap);
try {
expectZero(api._gmssl_sm4_cbc_padding_encrypt(keyPtr, ivPtr, inPtr, plain.length, encPtr, encCap, encLenPtr), "sm4_cbc_encrypt");
const encLen = api.HEAPU32[encLenPtr >> 2];
expectZero(api._gmssl_sm4_cbc_padding_decrypt(keyPtr, ivPtr, encPtr, encLen, decPtr, encCap, decLenPtr), "sm4_cbc_decrypt");
return read(decPtr, api.HEAPU32[decLenPtr >> 2]);
} finally {
api._free(encPtr); api._free(decPtr); api._free(encLenPtr); api._free(decLenPtr);
}
})));
}
function sm4Ctr(key, ctr, data) {
return withBytes(key, keyPtr => withBytes(ctr, ctrPtr => withBytes(data, inPtr => withOut(data.length, outPtr => {
expectZero(api._gmssl_sm4_ctr_encrypt(keyPtr, ctrPtr, inPtr, data.length, outPtr), "sm4_ctr");
return read(outPtr, data.length);
}))));
}
function sm4GcmRoundtrip(key, iv, aad, plain) {
return withBytes(key, keyPtr => withBytes(iv, ivPtr => withBytes(aad, aadPtr => withBytes(plain, plainPtr => {
const cipherPtr = api._malloc(plain.length || 1);
const tagPtr = api._malloc(16);
const outPtr = api._malloc(plain.length || 1);
try {
expectZero(api._gmssl_sm4_gcm_encrypt(keyPtr, ivPtr, iv.length, aadPtr, aad.length, plainPtr, plain.length, cipherPtr, tagPtr, 16), "sm4_gcm_encrypt");
expectZero(api._gmssl_sm4_gcm_decrypt(keyPtr, ivPtr, iv.length, aadPtr, aad.length, cipherPtr, plain.length, tagPtr, 16, outPtr), "sm4_gcm_decrypt");
return read(outPtr, plain.length);
} finally {
api._free(cipherPtr); api._free(tagPtr); api._free(outPtr);
}
}))));
}
function sm2Roundtrip(text) {
const msg = encoder.encode(text);
const id = encoder.encode("1234567812345678");
const privPtr = api._malloc(32);
const pubPtr = api._malloc(65);
const idPtr = alloc(id);
const msgPtr = alloc(msg);
const sigPtr = api._malloc(72);
const sigLenPtr = lenPtr();
const cipherPtr = api._malloc(366);
const cipherLenPtr = lenPtr();
const plainPtr = api._malloc(255);
const plainLenPtr = lenPtr();
const peerPrivPtr = api._malloc(32);
const peerPubPtr = api._malloc(65);
const secretAPtr = api._malloc(65);
const secretBPtr = api._malloc(65);
try {
expectZero(api._gmssl_sm2_generate_key(privPtr, pubPtr), "sm2_generate_key");
expectZero(api._gmssl_sm2_sign(privPtr, idPtr, id.length, msgPtr, msg.length, sigPtr, 72, sigLenPtr), "sm2_sign");
const sigLen = api.HEAPU32[sigLenPtr >> 2];
if (api._gmssl_sm2_verify(pubPtr, 65, idPtr, id.length, msgPtr, msg.length, sigPtr, sigLen) !== 1) {
throw new Error("sm2_verify returned invalid");
}
expectZero(api._gmssl_sm2_encrypt(pubPtr, 65, msgPtr, msg.length, cipherPtr, 366, cipherLenPtr), "sm2_encrypt");
expectZero(api._gmssl_sm2_decrypt(privPtr, cipherPtr, api.HEAPU32[cipherLenPtr >> 2], plainPtr, 255, plainLenPtr), "sm2_decrypt");
expectZero(api._gmssl_sm2_generate_key(peerPrivPtr, peerPubPtr), "sm2_generate_peer_key");
expectZero(api._gmssl_sm2_ecdh(privPtr, peerPubPtr, 65, secretAPtr), "sm2_ecdh_a");
expectZero(api._gmssl_sm2_ecdh(peerPrivPtr, pubPtr, 65, secretBPtr), "sm2_ecdh_b");
const plain = read(plainPtr, api.HEAPU32[plainLenPtr >> 2]);
return {
publicKey: toHex(read(pubPtr, 65)).slice(0, 40) + "...",
sigLen,
decrypted: decoder.decode(plain),
ecdhEqual: toHex(read(secretAPtr, 65)) === toHex(read(secretBPtr, 65))
};
} finally {
[privPtr, pubPtr, idPtr, msgPtr, sigPtr, sigLenPtr, cipherPtr, cipherLenPtr, plainPtr,
plainLenPtr, peerPrivPtr, peerPubPtr, secretAPtr, secretBPtr].forEach(api._free);
}
}
function zucRoundtrip(key, iv, plain) {
return withBytes(key, keyPtr => withBytes(iv, ivPtr => withBytes(plain, inPtr => {
const cipherPtr = api._malloc(plain.length || 1);
const outPtr = api._malloc(plain.length || 1);
const macPtr = api._malloc(4);
try {
expectZero(api._gmssl_zuc_encrypt(keyPtr, ivPtr, inPtr, plain.length, cipherPtr), "zuc_encrypt");
expectZero(api._gmssl_zuc_encrypt(keyPtr, ivPtr, cipherPtr, plain.length, outPtr), "zuc_decrypt");
expectZero(api._gmssl_zuc_mac(keyPtr, ivPtr, inPtr, plain.length, macPtr), "zuc_mac");
return { plain: read(outPtr, plain.length), mac: toHex(read(macPtr, 4)) };
} finally {
api._free(cipherPtr); api._free(outPtr); api._free(macPtr);
}
})));
}
function kdfSamples() {
const ikm = encoder.encode("input key material");
const salt = encoder.encode("salt");
const info = encoder.encode("gmssl wasm");
const pass = encoder.encode("password");
const prkPtr = api._malloc(32);
const prkLenPtr = lenPtr();
const okmPtr = api._malloc(42);
const pbkdfPtr = api._malloc(32);
try {
return withBytes(ikm, ikmPtr => withBytes(salt, saltPtr => withBytes(info, infoPtr => withBytes(pass, passPtr => {
expectZero(api._gmssl_sm3_hkdf_extract(saltPtr, salt.length, ikmPtr, ikm.length, prkPtr, prkLenPtr), "hkdf_extract");
expectZero(api._gmssl_sm3_hkdf_expand(prkPtr, api.HEAPU32[prkLenPtr >> 2], infoPtr, info.length, okmPtr, 42), "hkdf_expand");
expectZero(api._gmssl_pbkdf2_hmac_sm3(passPtr, pass.length, saltPtr, salt.length, 10000, pbkdfPtr, 32), "pbkdf2");
return { hkdf: toHex(read(okmPtr, 42)).slice(0, 32) + "...", pbkdf2: toHex(read(pbkdfPtr, 32)).slice(0, 32) + "..." };
}))));
} finally {
api._free(prkPtr); api._free(prkLenPtr); api._free(okmPtr); api._free(pbkdfPtr);
}
}
function base64Sample(text) {
const data = encoder.encode(text);
return withBytes(data, inPtr => {
const outPtr = api._malloc(data.length * 2 + 128);
const outLenPtr = lenPtr();
const decPtr = api._malloc(data.length + 128);
const decLenPtr = lenPtr();
try {
expectZero(api._gmssl_base64_encode(inPtr, data.length, outPtr, data.length * 2 + 128, outLenPtr), "base64_encode");
const b64Len = api.HEAPU32[outLenPtr >> 2];
expectZero(api._gmssl_base64_decode(outPtr, b64Len, decPtr, data.length + 128, decLenPtr), "base64_decode");
return { encoded: decoder.decode(read(outPtr, b64Len)).trim(), decoded: decoder.decode(read(decPtr, api.HEAPU32[decLenPtr >> 2])) };
} finally {
api._free(outPtr); api._free(outLenPtr); api._free(decPtr); api._free(decLenPtr);
}
});
}
return { sm3Hex, sm3HmacHex, sm4Block, sm4CbcRoundtrip, sm4Ctr, sm4GcmRoundtrip, sm2Roundtrip, zucRoundtrip, kdfSamples, base64Sample, bytesToHex };
}
async function run() {
results.textContent = "";
runButton.disabled = true;
try {
if (!globalThis.createGmSSL) await loadScript("./gmssl.js");
const api = await createGmSSL({ locateFile: path => path });
const c = makeClient(api);
let passed = 0;
let failed = 0;
const test = (name, fn) => {
try {
const detail = fn();
addResult(name, true, typeof detail === "string" ? detail : JSON.stringify(detail));
passed++;
} catch (err) {
addResult(name, false, err.message || String(err));
failed++;
}
};
summary.textContent = `已加载 ${api.UTF8ToString(api._gmssl_wasm_version_str())}`;
test("SM3", () => {
const hash = c.sm3Hex("abc");
if (hash !== "66c7f0f462eeedd9d1f2d46bdc10e4e24167c4875cf2f7a2297da02b8f4ba8e0") {
throw new Error(hash);
}
return hash;
});
test("SM3 HMAC", () => c.sm3HmacHex("key", "abc"));
test("SM4 Block", () => {
const key = fromHex("0123456789abcdeffedcba9876543210");
const plain = fromHex("0123456789abcdeffedcba9876543210");
const cipher = c.sm4Block(key, plain);
const hex = toHex(cipher);
if (hex !== "681edf34d206965e86b3e94f536e4246") throw new Error(hex);
const back = c.sm4Block(key, cipher, true);
if (toHex(back) !== toHex(plain)) throw new Error("decrypt mismatch");
return hex;
});
test("SM4 CBC", () => {
const out = c.sm4CbcRoundtrip(fromHex("00112233445566778899aabbccddeeff"), fromHex("0102030405060708090a0b0c0d0e0f10"), encoder.encode("GmSSL SM4 CBC padding"));
const text = decoder.decode(out);
if (text !== "GmSSL SM4 CBC padding") throw new Error(text);
return text;
});
test("SM4 CTR", () => {
const plain = encoder.encode("GmSSL SM4 CTR");
const key = fromHex("00112233445566778899aabbccddeeff");
const ctr = fromHex("0102030405060708090a0b0c0d0e0f10");
const cipher = c.sm4Ctr(key, ctr, plain);
const back = c.sm4Ctr(key, ctr, cipher);
const text = decoder.decode(back);
if (text !== "GmSSL SM4 CTR") throw new Error(text);
return toHex(cipher);
});
test("SM4 GCM", () => {
const out = c.sm4GcmRoundtrip(fromHex("00112233445566778899aabbccddeeff"), fromHex("0102030405060708090a0b0c"), encoder.encode("aad"), encoder.encode("GmSSL SM4 GCM"));
const text = decoder.decode(out);
if (text !== "GmSSL SM4 GCM") throw new Error(text);
return text;
});
test("SM2", () => {
const res = c.sm2Roundtrip("GmSSL SM2 message");
if (res.decrypted !== "GmSSL SM2 message" || !res.ecdhEqual) throw new Error(JSON.stringify(res));
return res;
});
test("ZUC", () => {
const res = c.zucRoundtrip(fromHex("00112233445566778899aabbccddeeff"), fromHex("000102030405060708090a0b0c0d0e0f"), encoder.encode("GmSSL ZUC"));
const text = decoder.decode(res.plain);
if (text !== "GmSSL ZUC") throw new Error(text);
return { mac: res.mac };
});
test("KDF", () => c.kdfSamples());
test("Base64", () => {
const res = c.base64Sample("GmSSL browser WASM");
if (res.decoded !== "GmSSL browser WASM") throw new Error(JSON.stringify(res));
return res.encoded;
});
summary.textContent = `完成:${passed} passed, ${failed} failed`;
} catch (err) {
summary.textContent = err.message || String(err);
} finally {
runButton.disabled = false;
}
}
runButton.addEventListener("click", run);
run();
</script>
</body>
</html>
3.4 运行测试
cd dist
python3 -m http.server 8080
浏览器访问:
text
http://localhost:8080
它会会加载 gmssl.js,调用导出的 WASM 函数,并执行以下完整测试:
- SM3
- SM3-HMAC
- SM3-KDF
- SM3-HKDF
- PBKDF2-HMAC-SM3
- SM4 block/CBC/CTR/GCM
- SM2 生成密钥、签名、验签、加密、解密、ECDH
- ZUC/ZUC256
- hex/base64
- rand/version
!note
如有导出层未涉及的函数,可修改
wasm_api/gmssl_wasm.c后重新生成浏览器可用的JS/WASM
dist 目录已在 windows 和 ubuntu 系统下测试,测试结果:
