SM2 椭圆曲线公钥密码算法
SM2 椭圆曲线公钥密码算法
提示
提示:本章要点
- 概述
- 快速开始
- 加密与解密
- 数字签名
- 密钥交换
概述
SM2 是基于 256 位椭圆曲线的公钥密码算法,覆盖数字签名、密钥交换、公钥加密三大能力。
它在同等安全等级下密钥更短,适合带宽与存储敏感的业务场景,并强调“身份绑定”的签名语义。
参考标准
| 项目 | 说明 |
|---|---|
| GM/T 0003-2012 | SM2 椭圆曲线公钥密码算法 |
| GM/T 0009-2023 | SM2 密码算法使用规范(替代 GM/T 0009-2012) |
关键要点
| 项目 | 说明 |
|---|---|
| 安全强度 | 256 位椭圆曲线安全级别约等于 RSA-3072 |
| 典型用法 | SM2 加密对称密钥,SM4 处理大数据 |
| 身份绑定 | 签名包含用户标识(userId),需严格一致 |
商密场景中的 SM2
在商密体系中,SM2 通常承担“身份与信任”的角色:
签名用于确认“是谁发起”,加密用于保护“密钥本身”,密钥交换用于建立安全会话。
与外部系统对接时,优先确认 userId、密文模式、公钥格式 三项约定。
安全与性能
SM2 的加密/签名属于非对称运算,性能开销明显高于对称加密。
在大数据场景中建议使用“SM2 + SM4”混合加密以兼顾安全与效率。
快速开始
密钥对生成
import { generateKeyPair } from 'gmkitx';
// 生成非压缩格式密钥对(默认)
const { publicKey, privateKey } = generateKeyPair();
console.log('公钥:', publicKey); // 130位十六进制字符串,04开头
console.log('私钥:', privateKey); // 64位十六进制字符串
// 生成压缩格式密钥对
const compressed = generateKeyPair(true);
console.log('压缩公钥:', compressed.publicKey); // 66位十六进制字符串,02或03开头从私钥导出公钥
import { getPublicKeyFromPrivateKey } from 'gmkitx';
const privateKey = '228049e009de869baf9aba74f8f8c52e09cde1b52cafb0df7ab154ba4593743e';
// 导出非压缩公钥
const publicKey = getPublicKeyFromPrivateKey(privateKey);
// 导出压缩公钥
const compressedPubKey = getPublicKeyFromPrivateKey(privateKey, true);公钥压缩与解压
import { compressPublicKey, decompressPublicKey } from 'gmkitx';
// 压缩公钥(130位 -> 66位)
const compressed = compressPublicKey(publicKey);
// 解压公钥(66位 -> 130位)
const uncompressed = decompressPublicKey(compressed);加密与解密
SM2 支持非对称加密,使用公钥加密、私钥解密。
文本默认按 UTF-8 处理;如需加密二进制数据,请传入
Uint8Array。
基本用法
import { sm2Encrypt, sm2Decrypt } from 'gmkitx';
const { publicKey, privateKey } = generateKeyPair();
// 加密
const plaintext = 'Hello, SM2!';
const ciphertext = sm2Encrypt(publicKey, plaintext);
// 解密
const decrypted = sm2Decrypt(privateKey, ciphertext);
console.log(decrypted === plaintext); // true密文模式
SM2 密文由三部分组成:C1(曲线点)/ C2(密文)/ C3(哈希)。
GM/T 0009-2023 推荐 C1C3C2 排列:
$$
C = C1 || C3 || C2
$$
其中:
- C1:椭圆曲线点(非压缩 65 字节,压缩 33 字节)
- C2:密文数据(与明文等长)
- C3:SM3 摘要(32 字节)
import { sm2Encrypt, SM2CipherMode } from 'gmkitx';
// 使用 C1C3C2 模式(默认)
const cipher1 = sm2Encrypt(publicKey, plaintext, {
mode: SM2CipherMode.C1C3C2
});
// 使用 C1C2C3 模式
const cipher2 = sm2Encrypt(publicKey, plaintext, {
mode: SM2CipherMode.C1C2C3
});输出格式
支持十六进制与 Base64 输出,解密端可自动识别输入格式:
import { sm2Encrypt, sm2Decrypt, OutputFormat } from 'gmkitx';
// 十六进制输出(默认)
const hexCipher = sm2Encrypt(publicKey, plaintext, {
outputFormat: OutputFormat.HEX
});
// Base64 输出
const base64Cipher = sm2Encrypt(publicKey, plaintext, {
outputFormat: OutputFormat.BASE64
});
// 解密时自动检测 hex/base64
const plain1 = sm2Decrypt(privateKey, hexCipher);
const plain2 = sm2Decrypt(privateKey, base64Cipher);数字签名
SM2 支持数字签名和验签功能,确保数据完整性和来源可信。
基本签名
import { sign, verify, InputFormat, OutputFormat } from 'gmkitx';
const { publicKey, privateKey } = generateKeyPair();
const message = '重要消息';
// 签名
const signature = sign(privateKey, message);
// 验签
const isValid = verify(publicKey, message, signature);
console.log('签名有效:', isValid);签名/验签同样默认将字符串按 UTF-8 处理;二进制消息请使用
Uint8Array。
带用户 ID 的签名
SM2 签名支持用户标识符(User ID)。GM/T 0009-2023 推荐使用空字符串,GMKitX 为向后兼容保留默认值。
签名内部会先计算用户绑定的 Z 值(基于 userId 与公钥参数),避免身份与签名脱钩。
import { sign, verify, DEFAULT_USER_ID } from 'gmkitx';
const userId = '1234567812345678'; // 自定义用户 ID
// 使用自定义 userId 签名
const signature = sign(privateKey, message, {
userId
});
// 验签时也必须提供相同的 userId
const isValid = verify(publicKey, message, signature, {
userId
});注意
注意:如果不指定 userId,将使用默认值 DEFAULT_USER_ID = '1234567812345678'。如需严格对齐 GM/T 0009-2023,请显式传入 userId: ''。
签名格式
支持 DER 与 Raw 两种签名格式(默认 Raw):
import { sign, verify } from 'gmkitx';
// DER 格式(ASN.1 编码)
const derSig = sign(privateKey, message, { signatureFormat: 'der', outputFormat: OutputFormat.BASE64 });
// Raw 格式(r || s,128 位十六进制)
const rawSig = sign(privateKey, message, { signatureFormat: 'raw' });
// 如需自动识别可使用 signatureFormat: 'auto';互操作建议显式约定格式
const ok = verify(publicKey, message, derSig, { signatureFormat: 'der', inputFormat: InputFormat.BASE64 });密钥交换
SM2 密钥交换遵循 GM/T 0003.3/GM/T 0009 协议,包含长期密钥 + 临时密钥,支持相互认证与前向保密。
它不是简单 ECDH,需要交换临时公钥并按协议顺序执行。
import { generateKeyPair, keyExchange } from 'gmkitx';
// A/B 长期密钥对
const alice = generateKeyPair();
const bob = generateKeyPair();
// A/B 临时密钥对
const aliceTemp = generateKeyPair();
const bobTemp = generateKeyPair();
// A 先把临时公钥发给 B
const aliceTempPub = aliceTemp.publicKey;
const bobTempPub = bobTemp.publicKey;
// B 计算共享密钥,并返回自己的临时公钥
const resultB = keyExchange({
privateKey: bob.privateKey,
publicKey: bob.publicKey,
tempPrivateKey: bobTemp.privateKey,
peerPublicKey: alice.publicKey,
peerTempPublicKey: aliceTempPub,
isInitiator: false
});
// A 收到 B 的临时公钥后,完成协商
const resultA = keyExchange({
privateKey: alice.privateKey,
publicKey: alice.publicKey,
tempPrivateKey: aliceTemp.privateKey,
peerPublicKey: bob.publicKey,
peerTempPublicKey: resultB.tempPublicKey,
isInitiator: true
});
console.log(resultA.sharedKey === resultB.sharedKey); // true面向对象 API
除了函数式 API,gmkitx 还提供了面向对象的 API:
import { SM2 } from 'gmkitx';
// 从私钥创建实例
const sm2 = SM2.fromPrivateKey(privateKey);
// 加密
const ciphertext = sm2.encrypt('Hello, SM2!');
// 解密
const plaintext = sm2.decrypt(ciphertext);
// 签名
const signature = sm2.sign('Message');
// 验签
const isValid = sm2.verify('Message', signature);
// 获取公钥
const publicKey = sm2.getPublicKey();完整 API 参考
密钥管理
| 函数 | 说明 | 返回值 |
|---|---|---|
generateKeyPair(compressed?: boolean) | 生成 SM2 密钥对 | KeyPair |
getPublicKeyFromPrivateKey(privateKey, compressed?) | 从私钥导出公钥 | string |
compressPublicKey(publicKey) | 压缩公钥 | string |
decompressPublicKey(publicKey) | 解压公钥 | string |
加密解密
| 函数 | 说明 | 返回值 |
|---|---|---|
sm2Encrypt(publicKey, plaintext, options?) | SM2 加密 | string |
sm2Decrypt(privateKey, ciphertext, options?) | SM2 解密 | string |
签名验签
| 函数 | 说明 | 返回值 |
|---|---|---|
sign(privateKey, message, options?) | SM2 签名 | string |
verify(publicKey, message, signature, options?) | SM2 验签 | boolean |
密钥交换
| 函数 | 说明 | 返回值 |
|---|---|---|
keyExchange(params) | SM2 密钥交换 | SM2KeyExchangeResult |
高级用法
自定义曲线参数
虽然通常使用标准 SM2 曲线,但也支持自定义曲线参数:
import { SM2 } from 'gmkitx';
const customParams = {
p: 'FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF',
a: 'FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFC',
b: '28E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCBD414D940E93',
Gx: '32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7',
Gy: 'BC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0',
n: 'FFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123'
};
// 使用自定义曲线参数(不推荐,除非有特殊需求)批量操作
// 批量生成密钥对
const keyPairs = Array.from({ length: 10 }, () => generateKeyPair());
// 批量签名
const signatures = messages.map(msg => sign(privateKey, msg));
// 批量验签
const results = messages.map((msg, i) =>
verify(publicKey, msg, signatures[i])
);注意事项
注意
注意:以下内容涉及安全性、互操作或易错点,建议上线前逐条核对。
| 项目 | 说明 |
|---|---|
| 私钥安全 | 私钥必须妥善保管,泄露将导致安全风险 |
| 密钥长度 | SM2 私钥固定为 256 位(64 位十六进制) |
| 公钥格式 |
- 非压缩格式: 04 开头,130 位十六进制(65 字节)
- 压缩格式: 02 或 03 开头,66 位十六进制(33 字节)
| 项目 | 说明 |
|:--|:--|
| 用户 ID | 签名/验签必须使用相同 userId;GM/T 0009-2023 推荐'',库默认仍为DEFAULT_USER_ID|
| 密文模式 | C1C3C2 与 C1C2C3 必须一致;必要时显式指定模式 |
| 编码格式 | 输出为 hex/base64,解密端需匹配或使用自动识别 |
| ASN.1 密文 | 如密文以0x30开头,按 ASN.1 解析;与 Java/OpenSSL 互操作时常见 |
| 签名输入 | 默认会计算SM3(Z || M),不要自行先哈希;如需签名哈希,请使用skipZComputation|
| 大数据 | SM2 适合加密小数据;大数据请走 SM4 + SM2 混合加密 |
常见问题
Q: SM2 和 RSA 有什么区别?
A: SM2 是基于椭圆曲线的算法,相比 RSA:
- 更短的密钥长度(256位 vs 2048位)
- 更快的运算速度
- 更少的存储和带宽需求
- 安全强度相当或更高
Q: 如何选择密文模式?
A: 推荐使用 C1C3C2 模式(默认),这是 GM/T 0009-2023 标准推荐的模式。C1C2C3 模式主要用于兼容旧系统。
Q: 公钥压缩有什么好处?
A: 压缩公钥可以节省存储空间和传输带宽(从 65 字节减少到 33 字节),但需要额外的计算来解压。对于存储和传输敏感的场景推荐使用。
Q: 可以在浏览器中使用吗?
A: 是的,gmkitx 完全支持现代浏览器环境,不需要任何 polyfill。