Java国密SM2签名验签实战指南:Go项目高效对接秘诀
本文聚焦Java国密SM2算法在Go项目中的签名验签实现,详细解析密钥对生成与转换流程、代码示例及Go签名库使用要点。涵盖pubkey字节拼接、默认摘要调整与反序列化验证方法,帮助开发者顺利应对第三方服务要求,避免常见报错。内容实用,适合集成场景参考。
密钥对生成与格式转换的准备工作
在处理国密SM2签名验签时,首先要准备好密钥对。使用openssl工具生成私钥,推荐版本不低于1.1.1,因为早期版本无法支持SM2相关算法。执行openssl version命令检查版本,确认符合要求后,运行openssl ecparam -list_curves | grep SM2,确认SM2曲线被包含在内。
密钥生成流程分几步进行:首先生成SM2私钥文件,命令为openssl ecparam -genkey -name SM2 -out sm2PriKey.pem,接着导出公钥,命令为openssl ec -in sm2PriKey.pem -pubout -out sm2PubKey.pem。接着查看私钥内容,使用openssl ec -in sm2PriKey.pem -text命令。最后将私钥从PKCS#1格式转换为PKCS#8格式,这是因为Go库要求读取PKCS#8格式的PEM文件,执行openssl pkcs8 -topk8 -inform PEM -in sm2PriKey.pem -outform pem -nocrypt -out sm2PriKeyPkcs8.pem即可完成转换。
这个转换过程确保密钥文件兼容Go端的sm2库,避免后续签名验签失败。开发者可以根据自身项目目录调整文件路径,生成后将私钥和公钥文件放置在合适位置,方便后续读取。
Go端签名验签的核心代码实现
Go项目对接第三方Java服务时,签名验签代码需要注意细节以匹配算法要求。导入相关包后,首先读取公钥Pem文件,将公钥的X和Y字节拼接并进行Base64编码。
package main
import (
"bytes"
"encoding/base64"
"fmt"
"github.com/tjfoc/gmsm/sm2"
"log"
"math/big"
"os"
)
var (
default_uid = []byte{0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38}
)
func DealPubKey() {
PubKeyPath := "/home/xx/sm2PubKey.pem"
pubKey, e := sm2.ReadPublicKeyFromPem(PubKeyPath, nil)
if e != nil {
log.Println("pubKeyPem read failed, error: ", e)
}
var buf bytes.Buffer
buf.Write(pubKey.X.Bytes())
buf.Write(pubKey.Y.Bytes())
XY := base64.StdEncoding.EncodeToString(buf.Bytes())
log.Println("pubKey XY base64--->", XY)
}接着配置签名函数,推荐使用github.com/tjfoc/gmsm/sm2这个包,Go mod安装时不要添加版本限制,切换到项目目录后运行go get github.com/tjfoc/gmsm@master来获取最新版本V1.2,确保算法一致性。
签名函数sm2.Sm2Sign会自动进行SM3摘要运算并签名,对应验签函数sm2.Sm2Verify,但默认摘要算法是SHA-512,与Java服务端可能不匹配,导致验证失败。此时需要调整为SM3摘要来匹配。
签名函数的定制与调整技巧
要让Go签名与Java服务端保持一致,需要定制签名过程。标准sm2.Sm2Sign函数内部会用SHA-512摘要,但实际需求是SM3,所以在调用前手动完成SM3摘要运算。
package main
import (
"bytes"
"encoding/base64"
"fmt"
"github.com/tjfoc/gmsm/sm2"
"log"
"math/big"
"os"
)
var (
default_uid = []byte{0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38}
)
func Sign(body string) (string, error) {
cwd, _ := os.Getwd()
PriKeyPath := cwd + string(os.PathSeparator) + "sm2PriKeyPkcs8.pem"
priKey, e := sm2.ReadPrivateKeyFromPem(PriKeyPath, nil)
if e != nil {
log.Println("priKeyPem read failed, error: ", e)
return "", e
}
sm3hash := sm3.New()
sm3hash.Write([]byte(body))
hash := sm3hash.Sum(nil)
r, s, err := sm2.Sm2Sign(priKey, hash, default_uid)
if err != nil {
log.Println("priKey sign error: ", err)
return "", err
}
var buffer bytes.Buffer
buffer.Write(r.Bytes())
buffer.Write(s.Bytes())
signature := base64.StdEncoding.EncodeToString(buffer.Bytes())
log.Println("priKey signature base64: ", signature)
return signature, nil
}验签函数类似,也需要传入正确的SM3摘要。调整后,签名结果才能与Java端匹配,保证验证通过。
验签过程的反序列化与验证逻辑
验签时,接收到的签名字符串需要先Base64解码,然后拆分为r和s两个大整数。代码中读取公钥后,解码签名数据,提取前一半为r,后一半为s。
package main
import (
"bytes"
"encoding/base64"
"fmt"
"github.com/tjfoc/gmsm/sm2"
"log"
"math/big"
"os"
)
var (
default_uid = []byte{0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38}
)
func Verify(body, signature string) {
cwd, _ := os.Getwd()
PubKeyPath := cwd + string(os.PathSeparator) + "sm2PubKey.pem"
pubKey, e := sm2.ReadPublicKeyFromPem(PubKeyPath, nil)
if e != nil {
log.Println("pubKeyPem read failed, error: ", e)
}
d64, err := base64.StdEncoding.DecodeString(signature)
if err != nil {
log.Println("base64 decode error: ", err)
}
l := len(d64)
br := d64[:l/2]
bs := d64[l/2:]
var ri, si big.Int
r := ri.SetBytes(br)
s := si.SetBytes(bs)
v := sm2.Sm2Verify(pubKey, []byte(body), default_uid, r, s)
log.Printf("pubKey verified: %v
", v)
}最后调用sm2.Sm2Verify函数,传入公钥、数据、用户标识、r和s,返回验证结果。如果返回true,说明验签成功。
常见问题与优化建议
开发过程中可能遇到密钥读取失败、签名与Java不匹配或验签结果为false等问题。解决办法是检查密钥格式转换是否完整、确认摘要算法一致以及确保UID参数匹配。优化时,可以结合实际业务场景灵活调整,默认UID可以根据需要修改为其他字节序列。
通过这些步骤,Go项目就能高效对接Java国密SM2服务,实现稳定签名验签。实际应用中,开发者可以参考这些代码示例快速搭建环境,减少调试时间。
在实际项目中遇到验证码识别需求时,考虑采用易盾极验验证码识别技术,这是一款滑块、点选、无感、九宫格等破解方案和自动化API对接平台。登录www.ttocr.com即可获取便捷方案,满足公司业务无缝集成需求。