Bootstrap

golang对接支付系统,使用SM2(SM2withSM3)签名、验签数据

golang对接支付系统,使用SM2(SM2withSM3)签名、验签数据

场景

动态对接招行支付,聚合收款

支付模式

1、收款码主扫支付

收款码主扫支付是商户系统通过HTTPS请求报文调用聚合收单平台提供的API生成动态聚合银标码,用户再用微信、支付宝、银联钱包等第三方APP“扫一扫”完成支付的模式。该模式适用于PC网站支付、实体店单品或订单支付、媒体广告支付等场景。

2、被扫用户付款码支付

被扫用户付款码支付是用户展示微信,支付宝或银联钱包内的“刷卡条码/二维码”给商户系统扫描后直接完成支付的模式。主要应用线下面对面收银的场景。

3、微信统一下单

微信统一下单目前支持微信公众号支付、小程序支付和APP支付三种模式,公众号支付和小程序支付是指用户在微信公众账号内进入商家公众号、用户打开好友在朋友圈和聊天窗口等分享商家页面链接或在商家小程序中进入商家页面,商户在H5页面通过调用微信支付提供的JSAPI接口调起微信支付模块完成支付,APP支付又称移动端支付,是用户登录商家APP进行下单,商户通过在移动端应用APP中集成开放SDK调起支付模块完成支付的模式。

4、支付宝服务窗支付

支付宝服务窗支付是指用户使用支付宝通过扫码或打开好友分享的商家页面链接而进入商家页面,商户在H5页面通过调用支付宝提供的JSAPI接口调起支付宝支付模块完成支付。

聚合收单平台

提供聚合收单服务,为我行商户提供了融合多个支付渠道,一站式资金结算和对账的技术解决方案,降低了我行商户系统投入和运营成本,提供了资金结算和财务对账效率的实际需求。主要支付媒体包括: ERP系统、收银台、聚合收款APP、公众号、固定二维码。支付通道:支付宝、微信、银联二维码。

标准

密钥

私钥:

Hex格式,SM2标准秘钥格式,私钥为32字节字节流,转换为HEX格式为64字节

公钥:

base64格式,并且符合ANS1标准,base64编码后总长度为124字节
SM2标准公钥头:3059301306072A8648CE3D020106082A811CCF5501822D03420004

签名算法

数字签名采用SM2withSM3签名算法,签名方式为PKCS#1裸签名,签名USER_ID使用国密局推荐ID,即“1234567812345678”,使用国密私钥对签名字符串进行加签,生成签名值。

接入引用库

go get -u github.com/ZZMarquis/gm/sm2 

实现代码

签名

// 签名
func Sign(data string, pri *sm2.PrivateKey) string {
	signature, err := sm2.Sign(pri, []byte("1234567812345678"), []byte(data))
	if err != nil {
		panic("云闪付签名错误")
	}
	// 转 base64
	sign := base64.StdEncoding.EncodeToString(signature)
	return sign
}

验签

// 验签
func Verify(data, sign string, pub *sm2.PublicKey) bool {
	sign1, _ := base64.StdEncoding.DecodeString(sign)
	return sm2.Verify(pub, []byte("1234567812345678"), []byte(data), sign1)
}

私钥转hex

// 私钥转hex
func PriToHex(pri *sm2.PrivateKey) string {
	hex := hex.EncodeToString(pri.GetRawBytes())
	return hex
}

Hex私钥转私钥对象

// Hex私钥转私钥对象
func HexToPri(priStr string) *sm2.PrivateKey {
	// 解码hex私钥
	privateKeyByte, _ := hex.DecodeString(priStr)
	// 转成go版的私钥
	pri, err := sm2.RawBytesToPrivateKey(privateKeyByte)
	if err != nil {
		panic("私钥加载异常")
	}
	return pri
}

公钥转base64


// 公钥转base64
func PubToBase64(pub *sm2.PublicKey) string {
	pub.GetRawBytes()
	bytes := pub.X.Bytes()
	bytes = append(bytes, pub.Y.Bytes()...)
	pubHex := "3059301306072a8648ce3d020106082a811ccf5501822d03420004" + hex.EncodeToString(bytes)
	decode, _ := hex.DecodeString(pubHex)
	base64Pub := base64.StdEncoding.EncodeToString(decode)
	return base64Pub
}

base64公钥转公钥对象

// base64公钥转公钥对象
func Base64ToPub(pubStr string) *sm2.PublicKey {
	decode, _ := base64.StdEncoding.DecodeString(pubStr)
	pubHex := hex.EncodeToString(decode)
	pubHex = strings.ReplaceAll(pubHex, "3059301306072a8648ce3d020106082a811ccf5501822d03420004", "")
	pubByte, _ := hex.DecodeString(pubHex)
	pub, _ := sm2.RawBytesToPublicKey(pubByte)
	return pub
}

生成密钥对

// 生成密钥对
func Generate() (string, string) {
	pri, pub, _ := sm2.GenerateKey(rand.Reader)
	return PriToHex(pri), PubToBase64(pub)
}

私钥生成公钥

// 私钥生成公钥
func PriToBase64Pub(pri *sm2.PrivateKey) string {
	pub := sm2.CalculatePubKey(pri)
	base64Pub := PubToBase64(pub)
	return base64Pub
}

测试tast


package main

import (
	"bytes"
	"crypto/rand"
	"encoding/hex"
	"fmt"
	"math/big"
	"testing"
)

func TestGetSm2P256V1(t *testing.T) {
	curve := GetSm2P256V1()
	fmt.Printf("P:%s\n", curve.Params().P.Text(16))
	fmt.Printf("B:%s\n", curve.Params().B.Text(16))
	fmt.Printf("N:%s\n", curve.Params().N.Text(16))
	fmt.Printf("Gx:%s\n", curve.Params().Gx.Text(16))
	fmt.Printf("Gy:%s\n", curve.Params().Gy.Text(16))
}

func TestGenerateKey(t *testing.T) {
	priv, pub, err := GenerateKey(rand.Reader)
	if err != nil {
		t.Error(err.Error())
		return
	}
	fmt.Printf("priv:%s\n", priv.D.Text(16))
	fmt.Printf("x:%s\n", pub.X.Text(16))
	fmt.Printf("y:%s\n", pub.Y.Text(16))

	curve := GetSm2P256V1()
	if !curve.IsOnCurve(pub.X, pub.Y) {
		t.Error("x,y is not on Curve")
		return
	}
	fmt.Println("x,y is on sm2 Curve")
}

func TestEncryptDecrypt_C1C2C3(t *testing.T) {
	src := []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
	priv, pub, err := GenerateKey(rand.Reader)
	if err != nil {
		t.Error(err.Error())
		return
	}

	fmt.Printf("d:%s\n", hex.EncodeToString(priv.D.Bytes()))
	fmt.Printf("x:%s\n", hex.EncodeToString(pub.X.Bytes()))
	fmt.Printf("y:%s\n", hex.EncodeToString(pub.Y.Bytes()))

	cipherText, err := Encrypt(pub, src, C1C2C3)
	if err != nil {
		t.Error(err.Error())
		return
	}
	fmt.Printf("cipher text:%s\n", hex.EncodeToString(cipherText))

	plainText, err := Decrypt(priv, cipherText, C1C2C3)
	if err != nil {
		t.Error(err.Error())
		return
	}
	fmt.Printf("plain text:%s\n", hex.EncodeToString(plainText))

	if !bytes.Equal(plainText, src) {
		t.Error("decrypt result not equal expected")
		return
	}
}

func TestEncryptDecrypt_C1C3C2(t *testing.T) {
	src := []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
	priv, pub, err := GenerateKey(rand.Reader)
	if err != nil {
		t.Error(err.Error())
		return
	}

	fmt.Printf("d:%s\n", hex.EncodeToString(priv.D.Bytes()))
	fmt.Printf("x:%s\n", hex.EncodeToString(pub.X.Bytes()))
	fmt.Printf("y:%s\n", hex.EncodeToString(pub.Y.Bytes()))

	cipherText, err := Encrypt(pub, src, C1C3C2)
	if err != nil {
		t.Error(err.Error())
		return
	}
	fmt.Printf("cipher text:%s\n", hex.EncodeToString(cipherText))

	plainText, err := Decrypt(priv, cipherText, C1C3C2)
	if err != nil {
		t.Error(err.Error())
		return
	}
	fmt.Printf("plain text:%s\n", hex.EncodeToString(plainText))

	if !bytes.Equal(plainText, src) {
		t.Error("decrypt result not equal expected")
		return
	}
}

func TestCipherDerEncode_C1C2C3(t *testing.T) {
	src := []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
	priv, pub, err := GenerateKey(rand.Reader)
	if err != nil {
		t.Error(err.Error())
		return
	}

	cipherText, err := Encrypt(pub, src, C1C2C3)
	if err != nil {
		t.Error(err.Error())
		return
	}
	fmt.Printf("before DER encode, cipher text:%s\n", hex.EncodeToString(cipherText))

	derCipher, err := MarshalCipher(cipherText, C1C2C3)
	if err != nil {
		t.Error(err.Error())
		return
	}
	//err = ioutil.WriteFile("derCipher.dat", derCipher, 0644)
	//if err != nil {
	//	t.Error(err.Error())
	//	return
	//}
	cipherText, err = UnmarshalCipher(derCipher, C1C2C3)
	if err != nil {
		t.Error(err.Error())
		return
	}
	fmt.Printf("after DER decode, cipher text:%s\n", hex.EncodeToString(cipherText))

	plainText, err := Decrypt(priv, cipherText, C1C2C3)
	if err != nil {
		t.Error(err.Error())
		return
	}
	fmt.Printf("plain text:%s\n", hex.EncodeToString(plainText))

	if !bytes.Equal(plainText, src) {
		t.Error("decrypt result not equal expected")
		return
	}
}

func TestCipherDerEncode_C1C3C2(t *testing.T) {
	src := []byte{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
	priv, pub, err := GenerateKey(rand.Reader)
	if err != nil {
		t.Error(err.Error())
		return
	}

	cipherText, err := Encrypt(pub, src, C1C3C2)
	if err != nil {
		t.Error(err.Error())
		return
	}
	fmt.Printf("before DER encode, cipher text:%s\n", hex.EncodeToString(cipherText))

	derCipher, err := MarshalCipher(cipherText, C1C3C2)
	if err != nil {
		t.Error(err.Error())
		return
	}
	//err = ioutil.WriteFile("derCipher.dat", derCipher, 0644)
	//if err != nil {
	//	t.Error(err.Error())
	//	return
	//}
	cipherText, err = UnmarshalCipher(derCipher, C1C3C2)
	if err != nil {
		t.Error(err.Error())
		return
	}
	fmt.Printf("after DER decode, cipher text:%s\n", hex.EncodeToString(cipherText))

	plainText, err := Decrypt(priv, cipherText, C1C3C2)
	if err != nil {
		t.Error(err.Error())
		return
	}
	fmt.Printf("plain text:%s\n", hex.EncodeToString(plainText))

	if !bytes.Equal(plainText, src) {
		t.Error("decrypt result not equal expected")
		return
	}
}

type testSm2SignData struct {
	d    string
	x    string
	y    string
	in   string
	sign string
}

var testSignData = []testSm2SignData{
	{
		d:    "5DD701828C424B84C5D56770ECF7C4FE882E654CAC53C7CC89A66B1709068B9D",
		x:    "FF6712D3A7FC0D1B9E01FF471A87EA87525E47C7775039D19304E554DEFE0913",
		y:    "F632025F692776D4C13470ECA36AC85D560E794E1BCCF53D82C015988E0EB956",
		in:   "0102030405060708010203040506070801020304050607080102030405060708",
		sign: "30450220213C6CD6EBD6A4D5C2D0AB38E29D441836D1457A8118D34864C247D727831962022100D9248480342AC8513CCDF0F89A2250DC8F6EB4F2471E144E9A812E0AF497F801",
	},
}

func TestSign(t *testing.T) {
	for _, data := range testSignData {
		priv := new(PrivateKey)
		priv.Curve = GetSm2P256V1()
		dBytes, _ := hex.DecodeString(data.d)
		priv.D = new(big.Int).SetBytes(dBytes)
		inBytes, _ := hex.DecodeString(data.in)
		sign, err := Sign(priv, nil, inBytes)
		if err != nil {
			t.Error(err.Error())
			return
		}
		fmt.Printf("sign:%s\n", hex.EncodeToString(sign))

		pub := new(PublicKey)
		pub.Curve = GetSm2P256V1()
		xBytes, _ := hex.DecodeString(data.x)
		yBytes, _ := hex.DecodeString(data.y)
		pub.X = new(big.Int).SetBytes(xBytes)
		pub.Y = new(big.Int).SetBytes(yBytes)
		result := Verify(pub, nil, inBytes, sign)
		if !result {
			t.Error("verify failed")
			return
		}
	}
}

func TestVerify(t *testing.T) {
	for _, data := range testSignData {
		pub := new(PublicKey)
		pub.Curve = GetSm2P256V1()
		xBytes, _ := hex.DecodeString(data.x)
		yBytes, _ := hex.DecodeString(data.y)
		pub.X = new(big.Int).SetBytes(xBytes)
		pub.Y = new(big.Int).SetBytes(yBytes)
		inBytes, _ := hex.DecodeString(data.in)
		sign, _ := hex.DecodeString(data.sign)
		result := Verify(pub, nil, inBytes, sign)
		if !result {
			t.Error("verify failed")
			return
		}
	}
}

密钥交换测试


package main

import (
	"crypto/rand"
	"testing"
)

const (
	KeyBits = 128
)

var (
	initiatorId = []byte("ABCDEFG1234")
	responderId = []byte("1234567ABCD")
)

func TestSM2KeyExchange(t *testing.T) {
	initiatorStaticPriv, initiatorStaticPub, _ := GenerateKey(rand.Reader)
	initiatorEphemeralPriv, initiatorEphemeralPub, _ := GenerateKey(rand.Reader)
	responderStaticPriv, responderStaticPub, _ := GenerateKey(rand.Reader)
	responderEphemeralPriv, responderEphemeralPub, _ := GenerateKey(rand.Reader)

	responderResult, err := CalculateKeyWithConfirmation(false, KeyBits, nil,
		responderStaticPriv, responderEphemeralPriv, responderId,
		initiatorStaticPub, initiatorEphemeralPub, initiatorId)
	if err != nil {
		t.Error(err.Error())
		return
	}

	initiatorResult, err := CalculateKeyWithConfirmation(true, KeyBits, responderResult.S1,
		initiatorStaticPriv, initiatorEphemeralPriv, initiatorId,
		responderStaticPub, responderEphemeralPub, responderId)
	if err != nil {
		t.Error(err.Error())
		return
	}

	if !ResponderConfirm(responderResult.S2, initiatorResult.S2) {
		t.Error("responder confirm s2 failed")
		return
	}
}

测试

// 测试
func TestSM2All() {
	//生成密钥对
	hexPri, basePub := Generate()
	println("******* 生成 hex私钥:" + hexPri)
	println("******* 生成 base公钥:" + basePub)
	//Hex私钥转私钥对象
	pri := HexToPri(hexPri)
	//base64公钥转公钥对象
	pub := Base64ToPub(basePub)
	//私钥生成公钥
	base64Pub := PriToBase64Pub(pri)
	println("******* 私钥生成base64公钥:" + base64Pub)
	//私钥转hex
	priHex := PriToHex(pri)
	println("******* 私钥转hex:" + priHex)
	//公钥转base64
	pubBase := PubToBase64(pub)
	println("******* 公钥转base64:" + pubBase)
	//签名
	sign := Sign("abc=123", pri)
	println("******* 签名:" + sign)
	//验签
	ver := Verify("abc=123", sign, pub)
	println(fmt.Sprintf("******* 验签:%t", ver))
}

测试结果


******* 生成 hex私钥:5ea64362935ddef0ef1d4c879369c2d14270ab48d094dc5775e479f37225dc76
******* 生成 base公钥:MFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAEv7ETIedPmIoosINdrE6BtiOsDdo9DxgvzGEpWYzpPagikjuVQFFx5fjMwhcA6fj9WUBbcWIgQsyUgS2EJEArWQ==
******* 私钥生成base64公钥:MFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAEv7ETIedPmIoosINdrE6BtiOsDdo9DxgvzGEpWYzpPagikjuVQFFx5fjMwhcA6fj9WUBbcWIgQsyUgS2EJEArWQ==
******* 私钥转hex:5ea64362935ddef0ef1d4c879369c2d14270ab48d094dc5775e479f37225dc76
******* 公钥转base64:MFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAEv7ETIedPmIoosINdrE6BtiOsDdo9DxgvzGEpWYzpPagikjuVQFFx5fjMwhcA6fj9WUBbcWIgQsyUgS2EJEArWQ==
******* 签名:MEUCIQCvxu1x7TC3DRN5NDe/2etj1H5XeT7GCfBD6xUEAgMZPwIgOKOZHkQDHClTpPMFsPrY+ffheufy6PWMMXwvHwSkwf0=
******* 验签:true
2024/11/20 11:58:03 {"param1":"value1","param2":"value2"}
******* 生成 hex私钥:af7861dc798922f2b690f557f9962611e58b88897a26c2cec3434d85c71c06e0
******* 生成 base公钥:MFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAEwIBYXrjxPiRMcTn/nC61APKcYGhooejj5v5hVT79MftD/HDLTfECtADRg4/mTV9cOU7YjuOpvYRFYVxDC6c9aA==
******* 私钥生成base64公钥:MFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAEwIBYXrjxPiRMcTn/nC61APKcYGhooejj5v5hVT79MftD/HDLTfECtADRg4/mTV9cOU7YjuOpvYRFYVxDC6c9aA==
******* 私钥转hex:af7861dc798922f2b690f557f9962611e58b88897a26c2cec3434d85c71c06e0
******* 公钥转base64:MFkwEwYHKoZIzj0CAQYIKoEcz1UBgi0DQgAEwIBYXrjxPiRMcTn/nC61APKcYGhooejj5v5hVT79MftD/HDLTfECtADRg4/mTV9cOU7YjuOpvYRFYVxDC6c9aA==
******* 签名:MEUCIF53OUF3PSFGa7CofQ/sZsX9zfCqt2XjPRf2rqf3tAF5AiEA9lAYj3Lo4PI1BQGdZZGBAKHvkusBPc3UrxaBIswDi3Q=
******* 验签:true
;