近期go项目对接第三方Java服务,第三方要求使用国密sm3/sm2算法进行数据签名验签,特记录go端开发注意事项
密钥生成可以使用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
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
代码示例:
package main import ( "bytes" "encoding/base64" "fmt" "github.com/tjfoc/gmsm/sm2" "github.com/tjfoc/gmsm/sm3" "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) }