Skip to content

SM2 ​

SM2 是由中国国家密码管理局制定的椭圆曲线公钥密码算法(GM/T 0003-2012),是中国商用密码标准的核心算法之一。dongle 支持标准和流式 SM2 加密,提供多种密文格式和性能优化选项。

支持以下密文格式(通过 Sm2CipherMode 指定):

  • C1C3C2:国密标准推荐格式(默认),裸字节结构为 0x04 || C1(64字节) || C3(32字节) || C2(密文数据)
    • C1:椭圆曲线点(随机数生成)
    • C3:SM3 消息摘要(用于完整性验证)
    • C2:加密后的数据
  • C1C2C3:旧标准兼容格式,裸字节结构为 0x04 || C1(64字节) || C2(密文数据) || C3(32字节)
  • ASN1C1C3C2:ASN.1 DER 格式,密文为 SEQUENCE { x INTEGER, y INTEGER, C3 OCTET STRING, C2 OCTET STRING },即 BouncyCastle、Java 与多数国密网关的默认输出,不带 0x04 前缀
  • ASN1C1C2C3:ASN.1 DER 格式,密文为 SEQUENCE { x INTEGER, y INTEGER, C2 OCTET STRING, C3 OCTET STRING }

两种 ASN.1 格式均使用 cryptobyte 解析,数据被截断或不是合法 DER 时直接返回错误,不会被静默误读。

支持以下性能优化选项:

  • Window 窗口大小:控制椭圆曲线运算的预计算窗口(2-6),默认为 4
    • 窗口越大,加密速度越快,但内存占用略高
    • 推荐使用默认值 4 或 5 以获得最佳性能

注意事项:

  • 密钥格式:使用 PKCS#8 格式存储私钥,使用 SPKI/PKIX 格式存储公钥
  • 密文格式:加密和解密必须使用相同的 Sm2CipherMode(C1C3C2、C1C2C3、ASN1C1C3C2、ASN1C1C2C3 之一)
  • 密钥材料:SetPublicKey / SetPrivateKey 除 PEM 外,还接受 base64 或 hex 编码的 DER,以及裸密钥材料(公钥为 65 字节非压缩点,私钥为 32 字节标量),并统一归一化为 PEM
  • 数据安全:SM2 提供 128 位安全强度,相当于 RSA 3072 位
  • 互操作性:与 OpenSSL、BouncyCastle、Java 等库互操作时,需确认对方使用的密文格式;多数国密网关输出 ASN.1 格式
  • 私钥安全:私钥必须妥善保管,不能泄露
  • 标准符合:完全符合 GM/T 0003.4-2012(加密算法)和 GM/T 0003.5-2012(曲线参数)

导入相关模块:

go
import (
    "github.com/dromara/dongle"
    "github.com/dromara/dongle/crypto/keypair"
)

创建密钥对 ​

go
kp := keypair.NewSm2KeyPair()
// 设置密文格式(可选,默认为 C1C3C2)
kp.SetMode(keypair.C1C3C2)
// 与 BouncyCastle、Java 及多数国密网关互操作时使用 ASN.1 格式
// kp.SetMode(keypair.ASN1C1C3C2)
// 设置窗口大小(可选,默认为 4,范围 2-6)
kp.SetWindow(4)

生成密钥对 ​

go
// 生成 SM2 密钥对(256 位椭圆曲线)
err := kp.GenKeyPair()
if err != nil {
    panic(err)
}

// 获取 PEM 格式公钥
publicKey := kp.PublicKey  
// 获取 PEM 格式私钥
privateKey := kp.PrivateKey

从已有 PEM 格式密钥设置密钥对 ​

go
// 设置 PEM 格式公钥
kp.PublicKey = []byte(`-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAEv6TXzg5xkLbTkzQzmucCcGbVF1pv
BuOVxSa7tZEvT1bEKqF7WJx8UQ/a7R5YDslbMxPByZGqfPaeQEpwv3emQg==
-----END PUBLIC KEY-----`)

// 设置 PEM 格式私钥
kp.PrivateKey = []byte(`-----BEGIN PRIVATE KEY-----
MIGTAgEAMBMGByqGSM49AgEGCCqBHM9VAYItBHkwdwIBAQQgsXklJOzFBfCXQGaP
5kVornBxt1WjgUIv21Imv7ALAtygCgYIKoEcz1UBgi2hRANCAAS/pNfODnGQttOT
NDOa5wJwZtUXWm8G45XFJru1kS9PVsQqoXtYnHxRD9rtHlgOyVszE8HJkap89p5A
SnC/d6ZC
-----END PRIVATE KEY-----`)

从已有 DER 格式密钥设置密钥对 ​

除 base64 编码的 DER 外,SetPublicKey / SetPrivateKey 还接受 hex 编码的 DER 以及裸密钥材料(公钥为 65 字节非压缩点,私钥为 32 字节标量),并将结果统一归一化为 PEM。

go
// 设置 Base64 编码的 DER 格式公钥
kp.SetPublicKey([]byte("MFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAEv6TXzg5xkLbTkzQzmucCcGbVF1pvBuOVxSa7tZEvT1bEKqF7WJx8UQ/a7R5YDslbMxPByZGqfPaeQEpwv3emQg=="))

// 设置 Base64 编码的 DER 格式私钥
kp.SetPrivateKey([]byte("MIGTAgEAMBMGByqGSM49AgEGCCqBHM9VAYItBHkwdwIBAQQgsXklJOzFBfCXQGaP5kVornBxt1WjgUIv21Imv7ALAtygCgYIKoEcz1UBgi2hRANCAAS/pNfODnGQttOTNDOa5wJwZtUXWm8G45XFJru1kS9PVsQqoXtYnHxRD9rtHlgOyVszE8HJkap89p5ASnC/d6ZC"))

将 DER 格式密钥格式化成 PEM 格式 ​

go
// 将 base64 编码的 DER 格式公钥格式化为 PEM 格式
publicKey, err := kp.FormatPublicKey([]byte("MFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAEv6TXzg5xkLbTkzQzmucCcGbVF1pvBuOVxSa7tZEvT1bEKqF7WJx8UQ/a7R5YDslbMxPByZGqfPaeQEpwv3emQg=="))

// 将 base64 编码的 DER 格式私钥格式化为 PEM 格式
privateKey, err := kp.FormatPrivateKey([]byte("MIGTAgEAMBMGByqGSM49AgEGCCqBHM9VAYItBHkwdwIBAQQgsXklJOzFBfCXQGaP5kVornBxt1WjgUIv21Imv7ALAtygCgYIKoEcz1UBgi2hRANCAAS/pNfODnGQttOTNDOa5wJwZtUXWm8G45XFJru1kS9PVsQqoXtYnHxRD9rtHlgOyVszE8HJkap89p5ASnC/d6ZC"))

将 PEM 格式密钥压缩成 DER 格式 ​

go
// 将 PEM 格式公钥压缩成经过 base64 编码的 DER 格式(提取 PEM 中的 base64 编码 DER)
publicKey := kp.CompressPublicKey([]byte(`-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAEv6TXzg5xkLbTkzQzmucCcGbVF1pv
BuOVxSa7tZEvT1bEKqF7WJx8UQ/a7R5YDslbMxPByZGqfPaeQEpwv3emQg==
-----END PUBLIC KEY-----`))

// 将 PEM 格式私钥压缩成经过 base64 编码的 DER 格式(提取 PEM 中的 base64 编码 DER)
privateKey := kp.CompressPrivateKey([]byte(`-----BEGIN PRIVATE KEY-----
MIGTAgEAMBMGByqGSM49AgEGCCqBHM9VAYItBHkwdwIBAQQgsXklJOzFBfCXQGaP
5kVornBxt1WjgUIv21Imv7ALAtygCgYIKoEcz1UBgi2hRANCAAS/pNfODnGQttOT
NDOa5wJwZtUXWm8G45XFJru1kS9PVsQqoXtYnHxRD9rtHlgOyVszE8HJkap89p5A
SnC/d6ZC
-----END PRIVATE KEY-----`))

CompressPublicKey / CompressPrivateKey 通过 pem.Decode 解析输入,因此可以处理带有 Bag Attributes 等可选块头的 PEM。

公钥加密 ​

SM2 流式加密会在内部缓存全部明文,在 Close 时一次性加密输出。

输入数据

go
// 输入字符串
encrypter := dongle.Encrypt.FromString("hello world").BySm2(kp)
// 输入字节切片
encrypter := dongle.Encrypt.FromBytes([]byte("hello world")).BySm2(kp)
// 输入文件流
file, _ := os.Open("test.txt")
encrypter := dongle.Encrypt.FromFile(file).BySm2(kp)

// 检查加密错误
if encrypter.Error != nil {
	fmt.Printf("加密错误: %v\n", encrypter.Error)
	return
}

输出数据

go
// 输出 Hex 编码字符串
hexString := encrypter.ToHexString() // 例如:047fae94fd1a8b880d8d5454dd8df30c40...
// 输出 Hex 编码字节切片
hexBytes := encrypter.ToHexBytes()   // 例如:[]byte("047fae94fd1a8b880d8d5454dd8df30c40...")

// 输出 Base64 编码字符串
base64String := encrypter.ToBase64String() // 例如:BH+ulP0ai4gNjVRU3Y3zDEA=...
// 输出 Base64 编码字节切片
base64Bytes := encrypter.ToBase64Bytes()   // 例如:[]byte("BH+ulP0ai4gNjVRU3Y3zDEA=...")

// 输出未编码原始字符串
rawString := encrypter.ToRawString()
// 输出未编码原始字节切片
rawBytes := encrypter.ToRawBytes()

私钥解密 ​

输入数据

go
// 输入 Hex 编码字符串
decrypter := dongle.Decrypt.FromHexString(hexString).BySm2(kp)
// 输入 Hex 编码字节切片
decrypter := dongle.Decrypt.FromHexBytes(hexBytes).BySm2(kp)
// 输入 Hex 编码文件流
file, _ := os.Open("encrypted.hex")
decrypter := dongle.Decrypt.FromHexFile(file).BySm2(kp)

// 输入 Base64 编码字符串
decrypter := dongle.Decrypt.FromBase64String(base64String).BySm2(kp)
// 输入 Base64 编码字节切片
decrypter := dongle.Decrypt.FromBase64Bytes(base64Bytes).BySm2(kp)
// 输入 Base64 编码文件流
file, _ := os.Open("encrypted.base64")
decrypter := dongle.Decrypt.FromBase64File(file).BySm2(kp)

// 输入原始字符串
decrypter := dongle.Decrypt.FromRawString(rawString).BySm2(kp)
// 输入原始字节切片
decrypter := dongle.Decrypt.FromRawBytes(rawBytes).BySm2(kp)
// 输入原始文件流
file, _ := os.Open("encrypted.bin")
decrypter := dongle.Decrypt.FromRawFile(file).BySm2(kp)

// 检查解密错误
if decrypter.Error != nil {
	fmt.Printf("解密错误: %v\n", decrypter.Error)
	return
}

输出数据

go
// 输出解密后的字符串
decrypter.ToString() // hello world
// 输出解密后的字节切片
decrypter.ToBytes()  // []byte("hello world")

基于 MIT 许可发布,未经许可禁止任何形式的转载