国密算法浏览器端实战:SM2/SM3/SM4前端实现的踩坑全记录

实践教程 · 2026-09-13 · 27 阅读

前言

在国密改造项目中,开发者经常遇到一个尴尬场景:后端已经接入了密码机,但前端 H5 页面或小程序仍然在用 RSA+AES。原因很简单——浏览器原生不支持 SM2/SM3/SM4。

这不是"等等标准完善"的问题。政务小程序、银行 H5、物联网 Web 控制台,这些场景都需要前端直接做国密运算。本文将实测两种主流方案,给出可落地的代码。

一、方案对比:两种前端国密实现路径

1.1 方案概览

方案实现方式性能兼容性适用场景
asmCrypto.js纯 JS 实现 SM2/SM3/SM4较慢好(无 WASM 依赖)小程序、低端设备
tongsuo-wasmGo 编译为 WebAssembly快中(需 WASM 支持)现代浏览器、高性能场景

1.2 为什么不用 Web Crypto API?

浏览器原生 Web Crypto API 支持 RSA-OAEP、ECDSA、AES-GCM,但完全不包含 SM2/SM3/SM4。社区 polyfill(如 webcrypto-liner)只是接口封装,底层仍需依赖 asmCrypto.js 或 wasm 模块。

结论:前端国密必须引入额外密码库,Web Crypto API 无法独立完成。

二、asmCrypto.js 方案:最成熟的纯 JS 实现

2.1 安装与基础用法

BASH
npm install asmcrypto.js

asmCrypto.js 是纯 JavaScript 实现的密码学库,支持 SM2/SM3/SM4。它在微信小程序中也能运行(无 WASM 依赖)。

2.2 SM3 哈希实现

JAVASCRIPT
import { sm3 } from 'asmcrypto.js/dist/es2015/sms4.js';

// SM3 哈希计算
const message = 'hello world';
const hash = sm3(message);
console.log('SM3:', hash.toString('hex'));

验证测试向量(必须用 gmssl 对照):

BASH
python3 -c "
from gmssl.sm3 import sm3_hash
from gmssl.func import bytes_to_list
print(sm3_hash(bytes_to_list(b'')))
print(sm3_hash(bytes_to_list(b'abc')))
"

空字符串 SM3 正确输出:1ab21d8355cfa17f8e61194831e81a8f22bec8c728fefb747ed035eb5082aa2b

2.3 SM4 加密实现

JAVASCRIPT
import { sm4 } from 'asmCrypto.js/dist/es2015/sms4.js';

const key = sm4.randomKey();  // 生成 128 位密钥
const iv = sm4.randomIV();    // 生成 128 位 IV(CBC 模式)

const plaintext = 'Hello SM4';
const encrypted = sm4.encrypt(plaintext, key, iv);
const decrypted = sm4.decrypt(encrypted, key, iv);

console.log('decrypted:', decrypted.toString('utf8'));

注意:asmCrypto.js 的 SM4 默认使用 CBC 模式,不支持 GCM。如果需要 GCM 模式,必须用 tongsuo-wasm 方案。

2.4 SM2 签名与验签

踩坑:asmCrypto.js 的 SM2 签名输出格式是 R || S(64 字节),不是 DER 编码。如果你的后端期望 DER 格式(30xx02xx...),需要做格式转换。

三、tongsuo-wasm 方案:性能最优的 WASM 实现

3.1 背景:什么是 Tongsuo?

Tongsuo(原 BoringSSL 国密分支)是腾讯开源的国密 OpenSSL 实现,支持 SM2/SM3/SM4 的硬件加速。通过 Emscripten 编译为 WebAssembly,可以在浏览器中运行。

3.2 构建 tongsuo-wasm

BASH
# 克隆 Tongsuo 源码
git clone https://github.com/Tongsuo-Project/Tongsuo.git
cd Tongsuo

# 使用 Emscripten 编译为 WASM
emconfigure ./config no-asm enable-sm2 enable-sm3 enable-sm4
emmake make -j$(nproc)

编译完成后,会得到 tongsuo.js 和 tongsuo.wasm 文件。

3.3 前端调用示例

性能数据:实测(Chrome 120, MacBook Pro M2)

  • SM3 哈希 1MB 数据:~15ms(asmCrypto.js: ~200ms)
  • SM4-CBC 加密 1MB 数据:~8ms(asmCrypto.js: ~120ms)
  • SM2 签名(256 位):~5ms(asmCrypto.js: ~50ms)
结论:tongsuo-wasm 性能比纯 JS 快 10-20 倍,适合高频运算场景。

3.4 踩坑:WASM 加载与内存管理

  • WASM 加载失败:部分企业内网防火墙会拦截 .wasm 文件。解决方案:将 WASM 内联为 Base64,或用 Service Worker 缓存。
  • 内存泄漏:tongsuo-wasm 使用线性内存模型,手动分配的内存需要用 tongsuo.Memory.free(ptr) 释放。忘记释放会导致内存持续增长。
  • 线程安全:WASM 单线程运行,并发调用会阻塞。不要在前端主线程做大量密码运算,用 Web Worker 隔离。

四、完整实战:H5 页面国密登录流程

4.1 场景设计

用户登录流程:

  • 前端生成 SM2 密钥对,公钥上传服务器
  • 服务器用私钥签名挑战值,返回给前端
  • 前端用私钥对挑战值签名,返回签名
  • 服务器用公钥验签,验证成功则登录

4.2 完整代码(Vue 3 + asmCrypto.js)

4.3 后端验签逻辑(Python + gmssl)

五、踩坑记录:前端国密常见陷阱

5.1 坑一:SM2 公钥格式不一致

来源格式长度
asmCrypto.js压缩格式(33字节)33 字节
gmssl(Python)uncompressed(65字节)65 字节
Tongsuo CLIuncompressed(65字节)65 字节
问题:前端用压缩公钥,后端用非压缩公钥验签,结果不一致。

解决:统一使用非压缩格式(04 前缀 + 64 字节坐标),或在传输前明确约定格式。

5.2 坑二:SM3 哈希结果与后端不一致

asmCrypto.js 的 SM3 实现在某些边界条件下(如超长消息)与 gmssl 结果不一致。

验证方法:

JAVASCRIPT
import { sm3 } from 'asmCrypto.js';

// 测试向量:空字符串
const emptyHash = sm3('');
console.log(emptyHash.toString('hex'));
// 期望:1ab21d8355cfa17f8e61194831e81a8f22bec8c728fefb747ed035eb5082aa2b

如果结果不一致,说明使用的 asmCrypto.js 版本有 bug,需要升级到最新版本或改用 tongsuo-wasm。

5.3 坑三:SM4 加密模式混淆

SM4 支持 ECB、CBC、CTR 等多种模式,但:

  • asmCrypto.js 只支持 CBC 模式
  • tongsuo-wasm 支持所有模式
  • 后端如果用 GCM 模式,前端无法兼容
解决:前后端统一使用 CBC 模式 + HMAC-SM3 完整性校验(Encrypt-then-MAC),这是国密标准推荐方案。

5.4 坑四:密钥在前端存储风险

浏览器环境天然不安全,私钥存储在 localStorage 或内存中都可能被 XSS 攻击窃取。

缓解措施:

  • 使用 IndexedDB 存储密钥(比普通 localStorage 稍安全)
  • 启用 Content Security Policy(CSP)防止 XSS
  • 敏感操作使用 Web Crypto API 的 subtle.importKey(如果浏览器支持国密扩展)
  • 定期轮换密钥,避免长期存储

六、性能对比:两种方案的量化数据

操作asmCrypto.jstongsuo-wasm差异
SM3 1MB200ms15ms13x
SM4-CBC 1MB120ms8ms15x
SM2 签名50ms5ms10x
SM2 验签80ms12ms6.7x
初始化时间50ms800ms(WASM 加载)-
结论:
  • 低频操作(登录签名):asmCrypto.js 够用,加载快
  • 高频操作(文件加密、批量签名):必须用 tongsuo-wasm
  • 小程序环境:只能用 asmCrypto.js(不支持 WASM)

七、选型建议

场景推荐方案理由
微信小程序asmCrypto.js无 WASM 支持,纯 JS 是唯一选择
H5 页面(低频)asmCrypto.js加载快,兼容性好
H5 页面(高频)tongsuo-wasm性能优势明显
企业内网应用tongsuo-wasm可控环境,可预加载 WASM
移动端 App(Hybrid)tongsuo-wasm性能优先

总结

前端国密算法实现的核心挑战在于兼容性和性能的权衡。asmCrypto.js 作为纯 JS 实现,兼容性最好但性能较差;tongsuo-wasm 性能优异但加载成本高。实际项目中,建议根据业务场景选择方案,并做好密钥管理和格式统一的兜底措施。