98 lines
3.1 KiB
Go
98 lines
3.1 KiB
Go
package payment
|
||
|
||
import (
|
||
"crypto/rand"
|
||
"encoding/base64"
|
||
"encoding/hex"
|
||
"encoding/json"
|
||
"fmt"
|
||
"strings"
|
||
|
||
"github.com/ZZMarquis/gm/sm4"
|
||
"github.com/tjfoc/gmsm/sm2"
|
||
"github.com/tjfoc/gmsm/x509"
|
||
)
|
||
|
||
// EncryptRequest 加密请求
|
||
// 对齐 YouChuKoffee 的 EncryptRequest → postbank.Encrypt 流程:
|
||
// 1. 序列化完整 request({head, body} 一起)
|
||
// 2. SM4 CBC 加密 → base64 → addNewline(每76字符加 \r\n)
|
||
// 3. SM2 加密 SM4 密钥(使用 SOP 公钥)→ HEX 大写作为 encryptKey
|
||
// 4. signature = SM2_Sign(request_base64 + encryptKey + accessToken)
|
||
// 5. 返回 {request, signature, encryptKey, accessToken}
|
||
func EncryptRequest(request interface{}, cfg Config) (map[string]string, error) {
|
||
client := NewClient(cfg)
|
||
accessToken := ""
|
||
|
||
// Step 1: 序列化完整 request({head, body} 一起加密)
|
||
inputBytes, err := json.Marshal(request)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("序列化请求失败: %v", err)
|
||
}
|
||
inputJson := string(inputBytes)
|
||
|
||
// Step 2: SM4 CBC 加密
|
||
sm4Key := generateSM4Key()
|
||
iv := getSM4IV()
|
||
paddedData := pkcs5Padding([]byte(inputJson), sm4.BlockSize)
|
||
tmp, err := sm4.CBCEncrypt(sm4Key, iv, paddedData)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("SM4加密失败: %v", err)
|
||
}
|
||
responseMsg := base64.StdEncoding.EncodeToString(tmp)
|
||
responseMsg = addNewline(responseMsg) // 对齐 YouChuKoffee,每76字符加 \r\n
|
||
|
||
// Step 3: SM2 加密 SM4 密钥
|
||
sopPubKey, err := x509.ReadPublicKeyFromHex(cfg.SopPublicKey)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("读取SOP公钥失败: %v", err)
|
||
}
|
||
encryptKeyBytes, err := sm2.Encrypt(sopPubKey, sm4Key, rand.Reader, sm2.C1C3C2)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("SM2加密SM4密钥失败: %v", err)
|
||
}
|
||
encryptKey := strings.ToUpper(hex.EncodeToString(encryptKeyBytes))
|
||
|
||
// Step 4: 签名(对齐 YouChuKoffee sign():UserID = MerchantId)
|
||
signContent := fmt.Sprintf("%s%s%s", responseMsg, encryptKey, accessToken)
|
||
signature, err := client.signWithUserID(signContent, []byte(cfg.MerchantId))
|
||
if err != nil {
|
||
return nil, fmt.Errorf("生成签名失败: %v", err)
|
||
}
|
||
|
||
result := map[string]string{
|
||
"request": responseMsg,
|
||
"signature": signature,
|
||
"encryptKey": encryptKey,
|
||
"accessToken": accessToken,
|
||
}
|
||
return result, nil
|
||
}
|
||
|
||
// signWithUserID 使用指定 UserID 对内容进行 SM2 签名
|
||
// 对齐 YouChuKoffee 的 sign() + rSToSign():直接返回 r.Text(16) + "#" + s.Text(16),不做 base64 编码
|
||
func (c *Client) signWithUserID(content string, userId []byte) (string, error) {
|
||
privKey, err := x509.ReadPrivateKeyFromHex(c.cfg.PrivateKey)
|
||
if err != nil {
|
||
return "", fmt.Errorf("读取私钥失败: %v", err)
|
||
}
|
||
r, s, err := sm2.Sm2Sign(privKey, []byte(content), userId, rand.Reader)
|
||
if err != nil {
|
||
return "", fmt.Errorf("SM2签名失败: %v", err)
|
||
}
|
||
return r.Text(16) + "#" + s.Text(16), nil
|
||
}
|
||
|
||
// addNewline 每76个字符添加 \r\n,对齐 YouChuKoffee 的 postbank.addNewline
|
||
func addNewline(str string) string {
|
||
lineLength := 76
|
||
var result strings.Builder
|
||
for i := 0; i < len(str); i++ {
|
||
if i > 0 && i%lineLength == 0 {
|
||
result.WriteString("\r\n")
|
||
}
|
||
result.WriteByte(str[i])
|
||
}
|
||
return result.String()
|
||
}
|