gmssl编译wasm

文章目录

  • [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 编译成 wasmjs 文件

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 系统下测试,测试结果:

相关推荐
如君愿4 小时前
考研复习 Day 42 | 密码学--第五章 序列密码(上)
考研·密码学
RD_daoyi5 小时前
Google 网站收录全流程解析:抓取、索引与排名机制详解
前端·javascript·人工智能·学习·搜索引擎·html
高心星5 小时前
鸿蒙6.0应用开发——Web组件的生命周期
html·web组件·arkweb·鸿蒙6.0·harmonyos6.0
放逐者-保持本心,方可放逐5 小时前
Go + WebAssembly 构建树木数据统计分析系统
开发语言·golang·wasm·javascipt
reikocao5 小时前
ubuntu系统源
linux·运维·ubuntu
Sisphusssss5 小时前
DiskGenius 备份 Ubuntu 系统
linux·ubuntu·diskgenius
暗冰ཏོ5 小时前
2026前端开发资源整理大全:从基础学习到工程化实战的完整导航
前端·javascript·css·前端框架·html
vortex55 小时前
国密(商用密码)算法核心参数速查
算法·密码学
城南观北6 小时前
Ubuntu 解决 apt 锁占用 + 安装 net-tools + 搭建 SSH 远程连接(保姆级实操)
linux·ubuntu·ssh