安全说明
这一页讲本库替你做了什么,以及哪些事必须你自己做。
库替你把住的
随机数只来自宿主 CSPRNG
SM2 的签名随机数 k、SM9 的 r、密钥生成用的私钥 d —— 全部取自 globalThis.crypto.getRandomValues(Node 下退到 webcrypto)。 本库不内置 RNG,也不会在任何路径上把随机性降级成 Math.random()。
生成私钥用拒绝采样
椭圆曲线私钥按 64 位字切分从 256 位候选里重抽直到小于曲线阶,而不是取模 —— 取模会让分布偏斜。
公钥必须落在曲线上
sm2.encrypt 会先验证公钥满足 y² ≡ x³ + ax + b (mod p) 且坐标在素域内, 否则抛 InvalidPublicKeyError。这挡掉的是"无效曲线点 / 错误子群"这类攻击面。 sm2.verify 遇到不在曲线上的公钥直接返回 false。
密钥流复用会被拒绝
ZUC 的 zucEncrypt 记住用过的 (key, iv, counter),第二次出现同一个组合就抛 InvalidKeyError。同一密钥流异或两条明文即泄露两者的异或值 —— 这是静默失败里代价最高的 一种,所以宁可抛错。记录表有 4096 条上限(FIFO 淘汰),不会无限增长。
需要自己管理计数器的场景可以 configureReuseGuard({ enabled: false }) 关掉 —— 但那就把责任接过去了。
确认值用常数时间比较
SM2 / SM9 密钥交换里的确认值 S_A / S_B 校验是逐字节常数时间比较, 不因提前返回而泄露信息。
需要你自己决定的
SM2 公钥派生缓存(默认开启)
sign 每次都需要公钥算 Z 值,而公钥只依赖私钥。默认开启的进程内缓存(容量 32)让 sign 从 252 µs 降到 142 µs,代价是私钥的 hex 形态与其派生结果会驻留在进程内存里。
如果你的威胁模型不接受这个,关掉它:
import { sm2 } from 'sm-crypto-wasm';
sm2.configurePublicKeyCache({ enabled: false }); // 回到每次都重算的无状态行为
sm2.clearPublicKeyCache(); // 清掉已缓存的条目ECB 和"无认证的加密"
- ECB 泄露结构:相同明文块产出相同密文块。只在测试或兼容旧数据时用。
- CTR / OFB / ECB 没有完整性:密文被改动你不会得到错误,只会得到错误的明文。 需要机密性 + 完整性时用 GCM。
IV / nonce 的生成与唯一性
- GCM 的 IV 在同一密钥下绝不可重复,否则可恢复认证密钥。本库不会替你生成 IV —— 显式传入,并保证唯一性(常用做法:96 位随机数或严格递增的计数器)。
- CBC 的 IV 需要不可预测。
- ZUC 的
(key, iv, counter)同上(库会在加密路径上拦一道)。
密钥的存储与生命周期
本库只做算法,不做密钥管理:私钥以 PEM / hex 字符串进出,不落盘、不写日志是库的设计 约束,但把它写进 localStorage、日志或错误上报是你的代码的事。
不在范围
- 国密机 / HSM / PKCS#11 句柄;
- X.509 / PKCS#7 / CMS 协议封装(本库只到"算法 + 密钥编解码"这一层);
- 抗侧信道的极端要求:WASM 的常数时间性质不受本库完全控制(例如底层查表访问模式), 高价值目标请评估后再采用。
建议的默认组合
| 需求 | 选择 |
|---|---|
| 摘要 | SM3 |
| 对称加密 + 完整性 | SM4-GCM |
| 对称加密,只要机密性 | SM4-CTR(IV 唯一)或 SM4-CBC(IV 不可预测) |
| 数字签名 | SM2 + SM3withSM2 |
| 公钥加密 | SM2(C1C3C2) |
| 标识密码 / 无证书体系 | SM9 |
| 移动通信协议栈 | ZUC(EEA3 + EIA3) |