PaymentCenter/app/third/paymentService/psbc/client.go

253 lines
6.5 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package payment
import (
"crypto/hmac"
"crypto/rand"
"crypto/sha256"
"encoding/base64"
"encoding/hex"
"encoding/json"
"fmt"
"math/big"
"sort"
"strings"
"time"
"github.com/ZZMarquis/gm/sm4"
"github.com/tjfoc/gmsm/sm2"
"github.com/tjfoc/gmsm/x509"
)
// Client 支付客户端
type Client struct {
cfg Config
}
// NewClient 创建支付客户端
func NewClient(cfg Config) *Client {
return &Client{cfg: cfg}
}
// MapToString 将 map 转换为排序后的 key=value&key=value 格式
func MapToString(m map[string]string) string {
keys := make([]string, 0, len(m))
for k := range m {
keys = append(keys, k)
}
sort.Strings(keys)
var result []string
for _, k := range keys {
result = append(result, fmt.Sprintf("%s=%s", k, m[k]))
}
return strings.Join(result, "&")
}
// HmacSha256To16 生成 HMAC-SHA256 签名16进制
func HmacSha256To16(key string, data string) string {
mac := hmac.New(sha256.New, []byte(key))
mac.Write([]byte(data))
encode := mac.Sum(nil)
return hex.EncodeToString(encode)
}
// GenerateOrderNumber 生成订单号
func GenerateOrderNumber() string {
datePart := time.Now().Format("20060102150405")
randomPart := fmt.Sprintf("%06d", time.Now().Nanosecond()%1000000)
return "SJ" + datePart + randomPart
}
// RandomNumber 生成指定长度的随机数字字符串
func RandomNumber(n int) string {
result := make([]byte, n)
for i := 0; i < n; i++ {
result[i] = byte(time.Now().Nanosecond()%10 + '0')
time.Sleep(1)
}
return string(result)
}
// EncryptMobile 手机银行支付参数加密
// 返回: tysdPayParams(base64), tysdEncryptKey(base64)
func (c *Client) EncryptMobile(inputJson string, signStr string) (string, string, error) {
defer func() {
if err := recover(); err != nil {
fmt.Println("加密失败", err)
}
}()
sm4Key := generateSM4Key()
// SM2 加密 SM4 密钥(使用银行公钥)
bankPubKey, err := x509.ReadPublicKeyFromHex(c.cfg.BankKey)
if err != nil {
return "", "", fmt.Errorf("读取银行公钥失败: %v", err)
}
encryptKeyBytes, err := sm2.Encrypt(bankPubKey, sm4Key, nil, sm2.C1C3C2)
if err != nil {
return "", "", fmt.Errorf("sm2加密失败: %v", err)
}
encryptKey := hex.EncodeToString(encryptKeyBytes)
if len(encryptKey) > 2 && encryptKey[0:2] == "04" {
encryptKeyBytes = encryptKeyBytes[1:]
}
// SM2 签名
merchantPrivKey, err := x509.ReadPrivateKeyFromHex(c.cfg.PrivateKey)
if err != nil {
return "", "", fmt.Errorf("读取商户私钥失败: %v", err)
}
r, s, err := sm2.Sm2Sign(merchantPrivKey, []byte(signStr), []byte(c.cfg.MerchantId), rand.Reader)
if err != nil {
return "", "", fmt.Errorf("签名失败: %v", err)
}
sig := encodeBase64(rsToBytes(r, s))
// 拼接参数并 SM4 ECB 加密
param := fmt.Sprintf("%s&sign=%s", inputJson, sig)
tmp, err := sm4ECBEncrypt(sm4Key, pkcs5Padding([]byte(param), 16))
if err != nil {
return "", "", fmt.Errorf("sm4加密失败: %v", err)
}
return encodeBase64(tmp), encodeBase64(encryptKeyBytes), nil
}
// DecryptResponse 解密银行响应
func (c *Client) DecryptResponse(respJson string, isRequest bool) (string, error) {
var reqData map[string]string
if err := json.Unmarshal([]byte(respJson), &reqData); err != nil {
return "", err
}
reqData["accessToken"] = ""
var keys []string
if isRequest {
keys = []string{"request", "signature", "encryptKey", "accessToken"}
} else {
keys = []string{"response", "signature", "encryptKey", "accessToken"}
}
var inEncryptKey, inData, inSignature string
for _, key := range keys {
data, ok := reqData[key]
if !ok {
return "", fmt.Errorf("请求数据中不存在%s", key)
}
switch key {
case "request", "response":
inData = data
case "signature":
inSignature = data
case "encryptKey":
inEncryptKey = data
}
}
// 验签
checked := c.verify(fmt.Sprintf("%s%s%s", inData, inEncryptKey, ""), inSignature)
if !checked {
return "", fmt.Errorf("签名验证失败")
}
// 解密 SM4 密钥
privKey, err := x509.ReadPrivateKeyFromHex(c.cfg.PrivateKey)
if err != nil {
return "", fmt.Errorf("读取私钥失败: %v", err)
}
hexEncryptKey, err := hex.DecodeString(inEncryptKey)
if err != nil {
return "", fmt.Errorf("解密sm4key失败: %v", err)
}
sm4Key, err := sm2.Decrypt(privKey, hexEncryptKey, sm2.C1C3C2)
if err != nil {
return "", fmt.Errorf("解密sm2key失败: %v", err)
}
// 解密数据
request, _ := base64.StdEncoding.DecodeString(inData)
encryptedSm4Key, err := sm4.CBCDecrypt(sm4Key, getSM4IV(), request)
return string(pkcs5UnPadding(encryptedSm4Key)), nil
}
// VerifySignature 验签
func (c *Client) VerifySignature(content, signature string) bool {
return c.verify(content, signature)
}
func (c *Client) verify(content, signature string) bool {
pubKey, err := x509.ReadPublicKeyFromHex(c.cfg.SopPublicKey)
if err != nil {
panic(fmt.Sprintf("pubKeyBytes sm2 ReadPublicKeyFromHex err: %v", err))
}
r, s := signToRS(signature)
return sm2.Sm2Verify(pubKey, []byte(content), []byte(c.cfg.MerchantId), r, s)
}
func signToRS(signStr string) (*big.Int, *big.Int) {
sub := strings.Split(signStr, "#")
if len(sub) != 2 {
panic(fmt.Sprintf("err rs: %x", sub))
}
r, _ := new(big.Int).SetString(sub[0], 16)
s, _ := new(big.Int).SetString(sub[1], 16)
return r, s
}
func rsToBytes(r, s *big.Int) []byte {
rBytes := r.Bytes()
sBytes := s.Bytes()
result := make([]byte, 0, len(rBytes)+len(sBytes))
result = append(result, rBytes...)
result = append(result, sBytes...)
return result
}
func encodeBase64(data []byte) string {
return base64.StdEncoding.EncodeToString(data)
}
func getSM4IV() []byte {
return []byte("UISwD9fW6cFh9SNS")
}
func generateSM4Key() []byte {
return make([]byte, 16)
}
func pkcs5Padding(ciphertext []byte, blockSize int) []byte {
padding := blockSize - len(ciphertext)%blockSize
padtext := make([]byte, padding)
for i := 0; i < padding; i++ {
padtext[i] = byte(padding)
}
return append(ciphertext, padtext...)
}
func pkcs5UnPadding(origData []byte) []byte {
length := len(origData)
if length == 0 {
return origData
}
unpadding := int(origData[length-1])
if unpadding > length {
return origData
}
return origData[:(length - unpadding)]
}
func sm4ECBEncrypt(key, plaintext []byte) ([]byte, error) {
block, err := sm4.NewCipher(key)
if err != nil {
return nil, err
}
blockSize := block.BlockSize()
plaintext = pkcs5Padding(plaintext, blockSize)
ciphertext := make([]byte, len(plaintext))
for start := 0; start < len(plaintext); start += blockSize {
block.Encrypt(ciphertext[start:start+blockSize], plaintext[start:start+blockSize])
}
return ciphertext, nil
}