近期 go 项目对接第三方 Java 服务, 第三方要求使用国密 sm3/sm2 算法进行数据签名验签, 特记录 go 端开发注意事项
1 关于密钥对
密钥生成可以使用 openssl 库,openssl 版本至少是 1.1.1, 终端运行 openssl version
检查版本, 之前版本不支持 sm2/sm3, openssl 官网 https://www.openssl.org/source/
检查椭圆曲线是否包含 sm2, 如下所示是支持 sm2 的。
$ openssl ecparam -list_curves | grep SM2
SM2 : SM2 curve over a 256 bit prime field
密钥生成流程, 存在第四步的原因是 go 使用的库需要读取 pkcs#8 格式私钥 pem 文件:
1 生成 sm2 私钥: openssl ecparam -genkey -name SM2 -out sm2PriKey.pem
2 sm2 私钥导出公钥: openssl ec -in sm2PriKey.pem -pubout -out sm2PubKey.pem
3 查看私钥: openssl ec -in sm2PriKey.pem -text
4 私钥 pkcs#1 转 pkcs#8: openssl pkcs8 -topk8 -inform PEM -in sm2PriKey.pem -outform pem -nocrypt -out sm2PriKeyPkcs8.pem
2 go 签名验签代码
1 读取公钥 pem 文件, 将公钥 X / Y 的 bytes 拼接 base64 编码, 函数示例:
func DealPubKey() {
// 公钥 X / Y 的 bytes 拼接后 base64 编码
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)
}
2 sm2 签名验签使用的包 ”github.com/tjfoc/gmsm/sm2″, go mod 自动安装不会安装最新版本, go.mod 不要添加此包,
github 最新版本是是 V1.2, 切换到项目目录, 终端运行 go get github.com/tjfoc/gmsm@master
安装最新版本后会自动添加到 go.mod
3 “github.com/tjfoc/gmsm/sm2″ 使用签名函数为 sm2.Sm2Sign, 此函数会将输入的[]bytes 类型数据 sm3 摘要运算后进行 sm2 签名, 对应验签函数 sm2.Sm2Verify
4 函数 sm2.Sign 默认是使用 sha-512 进行摘要运算, 与服务端 Java 签名验签失败.
代码示例:
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
}
r, s, err := sm2.Sm2Sign(priKey, []byte(body), default_uid)
if err != nil {log.Println("priKey sign error:", err)
return "", err
}
//Buffer 是一个实现了读写方法的可变大小的字节缓冲
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
}
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\n", v)
}
func main() {body := `{"name":"mike","gender":"male"}`
signature, _ := Sign(body)
Verify(body, signature)
}