📖 提示: 可从国家标准全文公开系统在线阅读相关标准。
SM2是一种椭圆曲线公钥密码算法,与NIST P系列曲线(特别是P-256)相似。虽然NIST主要将ECDSA标准化用于签名,将ECDH用于密钥交换,但SM2提供了包括公钥加密在内的完整套件。下表对比了SM2与国际标准:
| 功能 | SM2 | NIST/SEC 1 |
|---|---|---|
| 数字签名 | SM2 签名算法 | ECDSA(SEC 1) |
| 密钥交换 | SM2 密钥交换协议 | ECMQV(SEC 1) |
| 公钥加密 | SM2 公钥加密算法 | ECIES(SEC 1 第5章) |
关键差异:
业界对RSA非对称加密安全性的担忧日益增加。椭圆曲线密码学以更小的密钥长度提供更好的安全边界:
⚠️ 最佳实践: 由于更好的安全边界、更小的密钥长度和改进的性能,现代应用应优先选择椭圆曲线密码学(ECC)而非RSA进行新实现。
使用 sm2.GenerateKey() 函数生成SM2密钥对:
import (
"crypto/rand"
"github.com/emmansun/gmsm/sm2"
)
// 生成新的SM2密钥对
priv, err := sm2.GenerateKey(rand.Reader)
if err != nil {
log.Fatalf("密钥对生成失败: %v", err)
}
密钥类型结构:
SM2私钥扩展了 ecdsa.PrivateKey 以实现SM2特定的方法:
// PrivateKey 表示一个 ECDSA SM2 私钥。
// 它实现了 crypto.Decrypter 和 crypto.Signer 接口。
type PrivateKey struct {
ecdsa.PrivateKey
// 额外的 SM2 特定字段
}
SM2公钥使用标准的 ecdsa.PublicKey 结构。
⚠️ 重要提示: 从Go v1.20开始,
ecdsa.PublicKey包含了一个ECDH()方法,该方法与SM2不兼容。对于SM2密钥,请使用sm2.PublicKeyToECDH()代替。
公钥通常以PEM编码文本形式传输:
import (
"encoding/pem"
"github.com/emmansun/gmsm/smx509"
)
func parsePublicKey(pemContent []byte) (*ecdsa.PublicKey, error) {
block, _ := pem.Decode(pemContent)
if block == nil {
return nil, errors.New("PEM块解析失败")
}
pub, err := smx509.ParsePKIXPublicKey(block.Bytes)
if err != nil {
return nil, err
}
// 类型断言为 *ecdsa.PublicKey
ecdsaPub, ok := pub.(*ecdsa.PublicKey)
if !ok {
return nil, errors.New("不是ECDSA公钥")
}
return ecdsaPub, nil
}
从非压缩点坐标构造公钥:
func ExampleNewPublicKey() {
// 非压缩点格式: 0x04 || X || Y
keypoints, _ := hex.DecodeString(
"048356e642a40ebd18d29ba3532fbd9f3bbee8f027c3f6f39a5ba2f870369f9988" +
"981f5efe55d1c5cdf6c0ef2b070847a14f7fdf4272a8df09c442f3058af94ba1")
pub, err := sm2.NewPublicKey(keypoints)
if err != nil {
log.Fatalf("公钥创建失败: %v", err)
}
// 通过序列化回去验证
marshaled := elliptic.Marshal(sm2.P256(), pub.X, pub.Y)
fmt.Printf("%x\n", marshaled)
// Output: 048356e642a40ebd18d29ba3532fbd9f3bbee8f027c3f6f39a5ba2f870369f9988981f5efe55d1c5cdf6c0ef2b070847a14f7fdf4272a8df09c442f3058af94ba1
}
替代方法:
ecdh.P256().NewPublicKey() - 仅支持非压缩格式sm2.P256() 曲线直接构造坐标私钥可以封装在多种格式中。合适的解析方法取决于具体格式(详细讨论):
| 格式 | 解析方法 | 说明 |
|---|---|---|
| RFC 5915 / SEC1 | smx509.ParseSM2PrivateKey() |
标准EC私钥格式 |
| PKCS#8(未加密) | smx509.ParsePKCS8PrivateKey() |
标准未加密私钥 |
| PKCS#8(加密) | pkcs8.ParsePKCS8PrivateKeySM2() |
处理加密和未加密 |
| PKCS#12 | github.com/emmansun/go-pkcs12 |
Microsoft PFX格式 |
| PKCS#7 / CMS | github.com/emmansun/gmsm/pkcs7 |
密码消息语法 |
| CFCA 自定义 | cfca.ParseSM2() |
CFCA特定的PKCS#12变体 |
| GB/T 35276-2017 | sm2.ParseEnvelopedPrivateKey() |
信封私钥(CSR响应) |
📝 提示: PEM文件通常在第一行标明格式(例如
-----BEGIN EC PRIVATE KEY-----)。ASN.1编码的密钥需要通过OID检查来识别格式。
PKCS#8 加密私钥:
import (
"github.com/emmansun/gmsm/pkcs8"
)
func parseEncryptedPrivateKey(pemData []byte, password []byte) (*sm2.PrivateKey, error) {
block, _ := pem.Decode(pemData)
if block == nil {
return nil, errors.New("PEM解码失败")
}
priv, err := pkcs8.ParsePKCS8PrivateKeySM2(block.Bytes, password)
if err != nil {
return nil, fmt.Errorf("私钥解析失败: %w", err)
}
return priv, nil
}
GB/T 35276-2017 信封私钥:
典型使用场景:CA证书响应包含签名证书、CA生成的加密私钥和加密证书:
import (
"github.com/emmansun/gmsm/sm2"
)
func parseEnvelopedPrivateKey(envelopedData []byte, decryptKey *sm2.PrivateKey) (*sm2.PrivateKey, error) {
priv, err := sm2.ParseEnvelopedPrivateKey(envelopedData, decryptKey)
if err != nil {
return nil, fmt.Errorf("信封私钥解析失败: %w", err)
}
return priv, nil
}
直接从标量字节构造私钥:
func ExampleNewPrivateKey() {
// 32字节标量形式的私钥
keyBytes, _ := hex.DecodeString(
"6c5a0a0b2eed3cbec3e4f1252bfe0e28c504a1c6bf1999eebb0af9ef0f8e6c85")
priv, err := sm2.NewPrivateKey(keyBytes)
if err != nil {
log.Fatalf("私钥创建失败: %v", err)
}
fmt.Printf("%x\n", priv.D.Bytes())
// Output: 6c5a0a0b2eed3cbec3e4f1252bfe0e28c504a1c6bf1999eebb0af9ef0f8e6c85
}
func ExampleNewPrivateKeyFromInt() {
key := big.NewInt(0x123456)
priv, err := sm2.NewPrivateKeyFromInt(key)
if err != nil {
log.Fatalf("私钥创建失败: %v", err)
}
fmt.Printf("%x\n", priv.D.Bytes())
// Output: 123456
}
替代方法:
ecdh.P256().NewPrivateKey() - 需要恰好32字节(必要时零填充)GM/T 0091-2020 本质上是 RFC 8018 (PKCS#5) 的中国定制版本,为 PBES/PBKDF/PBMAC 方案使用了不同的OID。然而,这些OID似乎未经注册,且标准本身存在不一致之处。
| 对象标识符 | 定义 |
|---|---|
1.2.156.10197.6.1.4.1.5 |
基于口令的密钥派生规范 |
1.2.156.10197.6.1.4.1.5.1 |
PBKDF(本质上是PBKDF2) |
1.2.156.10197.6.1.4.1.5.2 |
PBES(本质上是PBES2) |
1.2.156.10197.6.1.4.1.5.3 |
PBMAC(基于口令的MAC) |
id-hmacWithSM3 定义为 1.2.156.10197.1.401.3.1(未注册)1.2.156.10197.1.401,暗示从PKCS#12-MAC复制粘贴而非PBMAC1pbeWithSM3AndSM4-CBC 为 1.2.156.10197.6.1.4.1.12.1.1(暗示PBES1方法)id-hmacWithSM3 重新定义为 1.2.156.10197.1.401.2(与A.2矛盾)⚠️ 兼容性警告: 由于这些不一致性,与声称符合GM/T 0091-2020标准的产品进行互操作可能具有挑战性。常用的
id-hmacWithSM3OID 是1.2.156.10197.1.401.2。
SM2签名通过 Z 值在哈希计算中包含用户标识符(UID)。标准UID是 1234567812345678@(默认值)。
func ExamplePrivateKey_Sign() {
toSign := []byte("ShangMi SM2 Sign Standard")
// 加载或生成私钥
privKey, _ := hex.DecodeString(
"6c5a0a0b2eed3cbec3e4f1252bfe0e28c504a1c6bf1999eebb0af9ef0f8e6c85")
testkey, err := sm2.NewPrivateKey(privKey)
if err != nil {
log.Fatalf("私钥创建失败: %v", err)
}
// 使用默认SM2选项签名(包含Z值计算)
sig, err := testkey.Sign(rand.Reader, toSign, sm2.DefaultSM2SignerOpts)
if err != nil {
fmt.Fprintf(os.Stderr, "签名错误: %s\n", err)
return
}
fmt.Printf("签名: %x\n", sig)
}
对于非标准UID,创建自定义签名选项:
import "github.com/emmansun/gmsm/sm2"
customUID := []byte("customUserID@domain.com")
signerOpts := sm2.NewSM2SignerOption(true, customUID)
sig, err := privateKey.Sign(rand.Reader, message, signerOpts)
使用 sm2.Signer 接口的 SignWithSM2 方法进行显式SM2签名:
sig, err := privateKey.SignWithSM2(rand.Reader, uid, message)
接口对比:
Sign() - 来自 crypto.Signer 接口(标准Go密码学)SignWithSM2() - 来自 sm2.Signer 接口(SM2特定)使用 sm2.VerifyASN1WithSM2() 验证SM2签名:
func ExampleVerifyASN1WithSM2() {
// 解析或构造公钥
keypoints, _ := hex.DecodeString(
"048356e642a40ebd18d29ba3532fbd9f3bbee8f027c3f6f39a5ba2f870369f9988" +
"981f5efe55d1c5cdf6c0ef2b070847a14f7fdf4272a8df09c442f3058af94ba1")
publicKey, err := sm2.NewPublicKey(keypoints)
if err != nil {
log.Fatalf("公钥创建失败: %v", err)
}
message := []byte("ShangMi SM2 Sign Standard")
signature, _ := hex.DecodeString(
"304402205b3a799bd94c9063120d7286769220af6b0fa127009af3e873c0e8742edc5f89" +
"0220097968a4c8b040fd548d1456b33f470cabd8456bfea53e8a828f92f6d4bdcd77")
// 使用默认UID验证(nil = 使用默认值)
valid := sm2.VerifyASN1WithSM2(publicKey, nil, message, signature)
fmt.Printf("签名有效: %v\n", valid)
// Output: 签名有效: true
}
自定义 UID 验证:
customUID := []byte("customUserID@domain.com")
valid := sm2.VerifyASN1WithSM2(publicKey, customUID, message, signature)
为了与期望ECDSA风格签名(无Z值计算)的系统兼容:
// 自己预先计算哈希
hash := sm3.Sum(message)
// 直接对哈希签名(无Z值)
sig, err := privateKey.Sign(rand.Reader, hash[:], nil)
📝 提示: 当
SignerOpts为nil或不是SM2SignerOption时,输入被视为预计算的哈希,不进行Z值计算。
// 自己预先计算哈希(必须与签名时的哈希算法匹配)
hash := sm3.Sum(message)
// 不使用Z值验证
valid := sm2.VerifyASN1(publicKey, hash[:], signature)
⚠️ 重要提示: 确保签名和验证使用的哈希算法相同。
对于大文件,对哈希而非整个文件进行签名:
import (
"github.com/emmansun/gmsm/sm3"
"io"
)
func signLargeFile(file io.Reader, privateKey *sm2.PrivateKey, uid []byte) ([]byte, error) {
// 计算 Z 值
za, err := sm2.CalculateZA(privateKey.Public().(*ecdsa.PublicKey), uid)
if err != nil {
return nil, err
}
// 使用预先添加的 Z 值对文件进行哈希
h := sm3.New()
h.Write(za)
if _, err := io.Copy(h, file); err != nil {
return nil, err
}
hash := h.Sum(nil)
// 对哈希签名
return privateKey.Sign(rand.Reader, hash, nil)
}
💡 提示: 从 v0.24.0 开始,使用
sm2.CalculateSM2Hash()方便地进行包含Z值的哈希计算。
SM2密钥交换协议在两个包中可用:
| 包 | 说明 | 使用场景 |
|---|---|---|
sm2 |
传统实现 | 遗留兼容性 |
ecdh |
现代Go风格实现 | 新应用、TLS/TLCP |
两种实现都提供安全的密钥协商功能。ecdh 包遵循Go的现代密码学API设计模式。
📖 参考: 实际使用示例请参见 gotlcp TLS/TLCP实现。
⚠️ 提示: 密钥交换协议主要用于TLS/TLCP上下文。大多数应用层开发不需要直接使用密钥交换协议。
⚠️ 重要原则: 非对称加密不是为加密大量数据而设计的。它应该用于加密对称密钥,然后用对称密钥加密实际数据。这种模式用于:
- TLS/TLCP:加密会话密钥
- 信封加密:加密数据加密密钥(DEK)
SM2公钥加密支持两种密文格式:
| 格式 | 说明 | 结构 |
|---|---|---|
| ASN.1 | 标准编码 | ASN.1 DER结构 |
| 简单串接 | 简单字节串接 | C1‖C3‖C2(当前标准)或 C1‖C2‖C3(遗留) |
格式组成:
📝 历史说明: 2010年标准使用 C1‖C2‖C3 格式。2012年标准(GM/T 0003-2012)改为 C1‖C3‖C2,并在GB/T 32918-2016中得以维持。
func ExampleEncryptASN1() {
// 解析或加载公钥
keypoints, _ := hex.DecodeString(
"048356e642a40ebd18d29ba3532fbd9f3bbee8f027c3f6f39a5ba2f870369f9988" +
"981f5efe55d1c5cdf6c0ef2b070847a14f7fdf4272a8df09c442f3058af94ba1")
publicKey, err := sm2.NewPublicKey(keypoints)
if err != nil {
log.Fatalf("公钥创建失败: %v", err)
}
plaintext := []byte("send reinforcements, we're going to advance")
// 使用 ASN.1 格式加密
ciphertext, err := sm2.EncryptASN1(rand.Reader, publicKey, plaintext)
if err != nil {
fmt.Fprintf(os.Stderr, "加密错误: %s\n", err)
return
}
fmt.Printf("密文 (ASN.1): %x\n", ciphertext)
}
// 使用简单串接加密(默认 C1C3C2)
ciphertext, err := sm2.Encrypt(rand.Reader, publicKey, plaintext, nil)
if err != nil {
fmt.Fprintf(os.Stderr, "加密错误: %s\n", err)
return
}
fmt.Printf("密文 (C1C3C2): %x\n", ciphertext)
📝 提示: 将
EncrypterOpts传递为nil默认使用 C1‖C3‖C2 格式。
SM2私钥的 Decrypt() 方法自动检测密文格式:
func ExamplePrivateKey_Decrypt() {
ciphertext, _ := hex.DecodeString(
"308194022100bd31001ce8d39a4a0119ff96d71334cd12d8b75bbc780f5bfc6e1efab535e85a" +
"02201839c075ff8bf761dcbe185c9750816410517001d6a130f6ab97fb23337cce1504" +
"20ea82bd58d6a5394eb468a769ab48b6a26870ca075377eb06663780c920ea5ee00" +
"42be22abcf48e56ae9d29ac770d9de0d6b7094a874a2f8d26c26e0b1daaf4ff50a484b88163d04785b04585bb")
// 加载私钥
privKey, _ := hex.DecodeString(
"6c5a0a0b2eed3cbec3e4f1252bfe0e28c504a1c6bf1999eebb0af9ef0f8e6c85")
privateKey, err := sm2.NewPrivateKey(privKey)
if err != nil {
log.Fatalf("私钥创建失败: %v", err)
}
// 解密(自动检测 ASN.1 或 C1C3C2 格式)
plaintext, err := privateKey.Decrypt(nil, ciphertext, nil)
if err != nil {
fmt.Fprintf(os.Stderr, "解密错误: %s\n", err)
return
}
fmt.Printf("明文: %s\n", string(plaintext))
// Output: 明文: send reinforcements, we're going to advance
}
遗留 C1C2C3 格式:
对于遗留的 C1‖C2‖C3 密文,显式指定格式:
import "github.com/emmansun/gmsm/sm2"
// 指定 C1C2C3 格式
opts := &sm2.DecrypterOpts{
CiphertextEncoding: sm2.C1C2C3,
}
plaintext, err := privateKey.Decrypt(nil, ciphertext, opts)
替代方法: 使用辅助函数在解密前转换密文格式。
sm2 包提供格式转换的实用函数:
import "github.com/emmansun/gmsm/sm2"
// ASN.1 转简单串接(C1C3C2)
plainCiphertext, err := sm2.ASN1Ciphertext2Plain(asn1Ciphertext, nil)
// 简单串接转 ASN.1
asn1Ciphertext, err := sm2.PlainCiphertext2ASN1(plainCiphertext, sm2.C1C3C2)
// 在 C1C2C3 和 C1C3C2 之间转换
convertedCiphertext, err := sm2.AdjustCiphertextSplicingOrder(
ciphertext,
sm2.C1C2C3, // 源格式
sm2.C1C3C2, // 目标格式
)
所有SM2标准(从2010年密码管理局版本到GB/T 32918-2016)都在第1部分第4章中一致定义了点到字节串的转换。这遵循 SEC 1: Elliptic Curve Cryptography (Version 2.0) 第2.3.3节规范。
标准格式:
0x04 || X || Y(SM2为65字节)0x02 || X 或 0x03 || X(SM2为33字节)0x06 || X || Y 或 0x07 || X || Y⚠️ 说明: 一些实现使用固定的64字节表示(省略格式指示符)。这是非标准的,会导致互操作性问题。请始终遵循SEC 1规范进行正确编码。
省略的原因:
后果:
✅ 最佳实践: 始终按照SEC 1的规定包含格式指示符。为了最大兼容性,使用非压缩格式(
0x04)并正确编码。
从 v0.27.0 开始,对大数据加密/解密实施了显著的性能改进:
优化项:
详细基准测试和性能分析请参考 SM2加密/解密性能。
📊 性能提示: 对于加密大量数据,使用信封加密(SM2加密对称密钥,然后使用SM4加密实际数据)。
中国主要云服务提供商提供SM2密钥管理服务。典型集成模式:
| 操作 | 位置 | 密钥类型 |
|---|---|---|
| 签名 | KMS API调用 | 私钥(在KMS中) |
| 验证 | 本地 | 公钥 |
| 加密 | 本地 | 公钥 |
| 解密 | KMS API调用 | 私钥(在KMS中) |
大多数KMS服务要求对预哈希数据进行签名。SM2签名需要包含 Z 值的特殊哈希计算:
import (
"github.com/emmansun/gmsm/sm2"
"github.com/emmansun/gmsm/sm3"
)
func calculateSM2HashForKMS(pub *ecdsa.PublicKey, data, uid []byte) ([]byte, error) {
// 如果未指定,使用默认UID
if len(uid) == 0 {
uid = []byte("1234567812345678")
}
// 计算 ZA(Z值)
za, err := sm2.CalculateZA(pub, uid)
if err != nil {
return nil, err
}
// 哈希: SM3(ZA || message)
h := sm3.New()
h.Write(za)
h.Write(data)
return h.Sum(nil), nil
}
💡 便捷函数: 从 v0.24.0 开始,直接使用
sm2.CalculateSM2Hash():
hash, err := sm2.CalculateSM2Hash(publicKey, data, uid)
公钥加密很简单 - 确保密文编码与KMS要求匹配:
// 大多数 KMS 服务使用 ASN.1 格式
ciphertext, err := sm2.EncryptASN1(rand.Reader, publicKey, plaintext)
if err != nil {
return nil, fmt.Errorf("加密失败: %w", err)
}
// 将密文发送到 KMS 进行解密
硬件密码模块(HSM)通常实现SDF(安全设备框架)或SKF(智能密钥框架)API。HSM中的私钥是不可导出的,但通过API提供签名和解密操作。
要与GMSM库集成,需要实现以下Go密码学接口:
crypto.Signer 接口type Signer interface {
// Public 返回与私钥对应的公钥
Public() crypto.PublicKey
// Sign 使用私钥对摘要进行签名
// 对于 SM2:摘要通常是预计算的哈希或原始消息
Sign(rand io.Reader, digest []byte, opts SignerOpts) (signature []byte, err error)
}
crypto.Decrypter 接口type Decrypter interface {
// Public 返回与私钥对应的公钥
Public() crypto.PublicKey
// Decrypt 使用私钥解密消息
Decrypt(rand io.Reader, msg []byte, opts DecrypterOpts) (plaintext []byte, err error)
}
func (h *HSMPrivateKey) Public() crypto.PublicKey {
// 返回与此私钥关联的公钥
// 应从HSM检索或在初始化期间存储
return h.publicKey
}
func (h *HSMPrivateKey) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) ([]byte, error) {
// 检查 opts 是否为 SM2 特定类型
if sm2Opts, ok := opts.(*sm2.SM2SignerOption); ok && sm2Opts.ForceGMSign {
// 将 digest 视为原始消息,计算 SM2 哈希
hash, err := sm2.CalculateSM2Hash(
h.Public().(*ecdsa.PublicKey),
digest,
sm2Opts.UID,
)
if err != nil {
return nil, err
}
// 调用 HSM API 对哈希签名
return h.hsmSignHash(hash)
}
// 将 digest 视为预计算的哈希
return h.hsmSignHash(digest)
}
func (h *HSMPrivateKey) hsmSignHash(hash []byte) ([]byte, error) {
// 调用 SDF/SKF API 执行签名
// 示例: SDF_InternalSign_ECC(sessionHandle, keyIndex, hash)
return h.sdkClient.Sign(h.keyHandle, hash)
}
重要考虑事项:
opts 是 *sm2.SM2SignerOption,计算SM2哈希(包含Z值)随机数源:HSM通常有硬件随机数生成器。可以忽略 rand 参数。
func (h *HSMPrivateKey) Decrypt(rand io.Reader, msg []byte, opts crypto.DecrypterOpts) ([]byte, error) {
// 调用 SDF/SKF API 执行解密
// 示例: SDF_InternalDecrypt_ECC(sessionHandle, keyIndex, ciphertext)
plaintext, err := h.sdkClient.Decrypt(h.keyHandle, msg)
if err != nil {
return nil, fmt.Errorf("HSM解密失败: %w", err)
}
return plaintext, nil
}
package hsm
import (
"crypto"
"crypto/ecdsa"
"io"
"github.com/emmansun/gmsm/sm2"
)
// HSMPrivateKey 表示存储在 HSM 中的私钥
type HSMPrivateKey struct {
keyHandle int // HSM 密钥句柄/索引
publicKey *ecdsa.PublicKey // 关联的公钥
sdkClient *SDFClient // SDF/SKF SDK 客户端
}
// 编译时确保接口合规
var (
_ crypto.Signer = (*HSMPrivateKey)(nil)
_ crypto.Decrypter = (*HSMPrivateKey)(nil)
)
func (h *HSMPrivateKey) Public() crypto.PublicKey {
return h.publicKey
}
func (h *HSMPrivateKey) Sign(rand io.Reader, digest []byte, opts crypto.SignerOpts) ([]byte, error) {
var hash []byte
// 检查是否为 SM2 特定签名
if sm2Opts, ok := opts.(*sm2.SM2SignerOption); ok && sm2Opts.ForceGMSign {
// 计算 SM2 哈希(ZA || 消息)
var err error
hash, err = sm2.CalculateSM2Hash(h.publicKey, digest, sm2Opts.UID)
if err != nil {
return nil, err
}
} else {
// 按原样使用摘要(假定为预计算的哈希)
hash = digest
}
// 调用 HSM 签名函数
signature, err := h.sdkClient.InternalSign(h.keyHandle, hash)
if err != nil {
return nil, err
}
return signature, nil
}
func (h *HSMPrivateKey) Decrypt(rand io.Reader, msg []byte, opts crypto.DecrypterOpts) ([]byte, error) {
plaintext, err := h.sdkClient.InternalDecrypt(h.keyHandle, msg)
if err != nil {
return nil, err
}
return plaintext, nil
}
SDF (GB/T 36322-2018):安全设备框架
SDF_OpenDevice - 打开设备会话SDF_InternalSign_ECC - 内部密钥签名SDF_InternalDecrypt_ECC - 内部密钥解密SDF_ExportSignPublicKey_ECC - 导出公钥SKF (GB/T 35291-2017):智能密钥框架
SKF_ConnectDev - 连接到设备SKF_ECCSignData - 使用ECC密钥签名数据SKF_DecryptData - 解密数据📖 标准:完整的SDF API文档请参考GB/T 36322-2018(密码设备应用接口规范)。
SM2椭圆曲线密码学支持各种高级密码协议。虽然其中一些处于概念验证阶段,没有正式标准,但它们展示了椭圆曲线密码学的多功能性。
ECDSA签名(包括SM2)由两个整数组成:r 和 s。以太坊引入了额外的变量 v(恢复标识符),使签名成为 {r, s, v}。由于SM2签名仅使用随机点的X坐标(对N取模),可以从签名恢复多个公钥。
// RecoverPublicKeysFromSM2Signature 从给定的签名和哈希恢复两个或四个 SM2 公钥
func RecoverPublicKeysFromSM2Signature(hash, sig []byte) ([]*ecdsa.PublicKey, error)
恢复的公钥:
📝 提示: 通常只返回前两个公钥。后两个仅在
(r - e) mod N < P - 1 - N时存在。
使用场景:
基于SM2曲线的EC-ElGamal提供部分同态加密,支持对加密数据进行加法操作。
支持类型:
uint32 - 无符号32位整数int32 - 有符号32位整数性质:
E(a) + E(b) = E(a + b)k * E(a) = E(k * a)实现: github.com/emmansun/sm2elgamal
示例使用场景:
// 电子投票:无需解密即可累加加密的投票
encryptedVote1 := Encrypt(publicKey, 1) // 投票 "赞成"
encryptedVote2 := Encrypt(publicKey, 0) // 投票 "反对"
encryptedTotal := Add(encryptedVote1, encryptedVote2)
totalVotes := Decrypt(privateKey, encryptedTotal) // 结果: 1
环签名在群组内提供签名者匿名性。环中的任何人都可能产生该签名,但实际签名者保持匿名。
性质:
实现: github.com/emmansun/sm2rsign
使用场景:
虽然尚未实现,但SM2理论上可以支持:
| 扩展 | 状态 | 代码库 |
|---|---|---|
| 公钥恢复 | ✅ 已实现 | GMSM核心库 |
| EC-ElGamal PHE | ✅ POC可用 | sm2elgamal |
| 环签名 | ✅ POC可用 | sm2rsign |
| 确定性签名 | ⏳ 计划中 | - |
| ECVRF | ⏳ 计划中 | - |
| 盲签名 | ⏳ 研究中 | - |
| 门限签名 | ⏳ 研究中 | - |
| Pedersen承诺 | ⏳ 研究中 | - |
⚠️ 提示: 标记为POC(概念验证)的扩展是实验性的,缺乏正式标准。在没有彻底安全审查的情况下,不应在生产环境中使用。
完整的API文档请访问:GMSM API 文档
答: 对于新的中国国内应用,推荐使用SM2,原因如下:
对于国际应用,考虑使用NIST P-256或Ed25519以获得更广泛的兼容性。
答: 不可以。 SM2和ECDSA虽然都是椭圆曲线算法,但不兼容:
尝试将SM2密钥与ECDSA一起使用将导致无效签名。
答: Z值由库自动处理:
"1234567812345678"(16字节)sm2.NewSM2SignerOption(true, customUID)nil 作为 SignerOpts 进行仅哈希签名对于KMS集成,使用 sm2.CalculateSM2Hash() 计算包含Z值的哈希。
答: 这些是SM2密文的不同串接顺序:
库在解密期间自动检测格式。对于新实现,使用C1C3C2或ASN.1格式。
答: 不要直接使用SM2加密大文件。 使用信封加密:
这种方法更快、更安全,并遵循行业最佳实践。
本文档是GMSM项目的一部分,采用MIT许可证。详见主LICENSE文件。