YouChuKoffee/app/utils/postbank/sm.go

205 lines
5.4 KiB
Go

package postbank
import (
"crypto/rand"
"encoding/base64"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"github.com/ZZMarquis/gm/sm4"
"math/big"
"qteam/app/utils/postbank/internal/sm2"
"qteam/app/utils/postbank/internal/util"
"strings"
)
func checkInData(reqData map[string]string, key string) (string, error) {
data, ok := reqData[key]
if !ok {
return "", errors.New("请求数据中不存在" + key)
}
return data, nil
}
func Decrypt(merchantId, privateKey, sopPublicKey, respJson string, isRequest bool) (string, error) {
var reqData map[string]string
err := json.Unmarshal([]byte(respJson), &reqData)
if err != nil {
return "", err
}
reqData["accessToken"] = ""
keys := [4]string{}
if isRequest {
keys = [4]string{"request", "signature", "encryptKey", "accessToken"}
} else {
keys = [4]string{"response", "signature", "encryptKey", "accessToken"}
}
var inEncryptKey, inAccessToken, inData, inSignature string
for i := 0; i < 4; i++ {
data, err := checkInData(reqData, keys[i])
if err != nil {
return "", err
}
switch keys[i] {
case "request", "response":
inData = data
case "signature":
inSignature = data
case "encryptKey":
inEncryptKey = data
case "accessToken":
inAccessToken = data
}
}
checked := verify(fmt.Sprintf("%s%s%s", inData, inEncryptKey, inAccessToken), inSignature, sopPublicKey, merchantId)
if !checked {
return "", errors.New("签名验证失败")
}
priKey, err := sm2.ReadPrivateKeyFromHex(privateKey)
if err != nil {
return "", errors.New("读取私钥失败")
}
hexEncryptKey, err := hex.DecodeString(inEncryptKey)
if err != nil {
return "", errors.New("解密sm4key失败")
}
sm4Key, err := util.Sm2Decrypt(priKey, hexEncryptKey)
request, _ := base64.StdEncoding.DecodeString(inData)
encryptedSm4Key, err := sm4.CBCDecrypt(sm4Key, util.GetSM4IV(), request)
return string(util.Padding(encryptedSm4Key, 0)), nil
}
func Encrypt(merchantId, privateKey, sopPublicKey, inputJson, token string, isRequest bool) (string, error) {
sm4Key := util.GenerateSM4Key()
iv := util.GetSM4IV()
tmp, err := sm4.CBCEncrypt(sm4Key, iv, util.Padding([]byte(inputJson), 1))
if err != nil {
return "", err
}
responseMsg := base64.StdEncoding.EncodeToString(tmp)
responseMsg = addNewline(responseMsg)
pubKey, err := sm2.ReadPublicKeyFromHex(sopPublicKey)
if err != nil {
return "", errors.New("读取私钥失败")
}
encryptKeyBytes, err := util.Sm2Encrypt(pubKey, sm4Key)
encryptKey := strings.ToUpper(hex.EncodeToString(encryptKeyBytes))
//accessToken := util.GenAccessToken(token)
accessToken := token
signContent := fmt.Sprintf("%s%s%s", responseMsg, encryptKey, accessToken)
signature, err := sign(merchantId, privateKey, signContent)
var reqData map[string]string
if isRequest {
reqData = map[string]string{
"request": responseMsg,
"signature": signature,
"encryptKey": encryptKey,
"accessToken": accessToken,
}
} else {
reqData = map[string]string{
"response": responseMsg,
"signature": signature,
"encryptKey": encryptKey,
"accessToken": accessToken,
}
}
jsonStr, err := json.Marshal(reqData)
if err != nil {
return "", err
}
return string(jsonStr), err
}
// GenerateKey 生成密钥对
func GenerateKey() (string, string) {
pri, _ := sm2.GenerateKey(rand.Reader)
hexPri := pri.D.Text(16)
// 获取公钥
publicKeyHex := publicKeyToString(&pri.PublicKey)
return strings.ToUpper(hexPri), publicKeyHex
}
// publicKeyToString 公钥sm2.PublicKey转字符串(与java中org.bouncycastle.crypto生成的公私钥完全互通使用)
func publicKeyToString(publicKey *sm2.PublicKey) string {
xBytes := publicKey.X.Bytes()
yBytes := publicKey.Y.Bytes()
// 确保坐标字节切片长度相同
byteLen := len(xBytes)
if len(yBytes) > byteLen {
byteLen = len(yBytes)
}
// 为坐标补齐前导零
xBytes = append(make([]byte, byteLen-len(xBytes)), xBytes...)
yBytes = append(make([]byte, byteLen-len(yBytes)), yBytes...)
// 添加 "04" 前缀
publicKeyBytes := append([]byte{0x04}, append(xBytes, yBytes...)...)
return strings.ToUpper(hex.EncodeToString(publicKeyBytes))
}
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()
}
func sign(merchantId string, privateKeyHex string, signContent string) (string, error) {
privateKey, err := sm2.ReadPrivateKeyFromHex(privateKeyHex)
if err != nil {
return "", err
}
r, s, err := sm2.Sm2Sign(privateKey, []byte(signContent), []byte(merchantId), rand.Reader)
if err != nil {
return "", err
}
return rSToSign(r, s), nil
}
func verify(content string, signature string, publicKeyStr string, merchantId string) bool {
pubKey, err := sm2.ReadPublicKeyFromHex(publicKeyStr)
if err != nil {
panic(fmt.Sprintf("pubKeyBytes sm2 ReadPublicKeyFromHex err: %v", err))
}
r, s := signToRS(signature)
return sm2.Sm2Verify(pubKey, []byte(content), []byte(merchantId), r, s)
}
func signToRS(signStr string) (*big.Int, *big.Int) {
signSub := strings.Split(signStr, "#")
if len(signSub) != 2 {
panic(fmt.Sprintf("err rs: %x", signSub))
}
r, _ := new(big.Int).SetString(signSub[0], 16)
s, _ := new(big.Int).SetString(signSub[1], 16)
return r, s
}
func rSToSign(r *big.Int, s *big.Int) string {
rStr := r.Text(16)
sStr := s.Text(16)
return fmt.Sprintf("%s#%s", rStr, sStr)
}