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() }