golang 对接第三方接口 RSA 做签(加密) 验签(解密)

一、过程

1.调用第三方接口前,一般需要按规则将参数按key1=value1&key2=value2 阿斯克码排序,sign参数不参与加密

2.将排序并连接好的参数字符串通过我方的私钥证书(.pem)进行加密得到加密串,当然加密得到的是 \[\]byte 字节流,需要将字节流转换成base64字符串

3.将加密字符串赋值给sign参数,并与其他加密参数一起通过post (application/x-www-form-urlencoded )请求第三方接口。 当然,第三方与我方对接的时,会要求我方提供公约证书给到他们,用以拿到我方请求后,对我方请求参数进行延签。

4.同理,我方请求第三方后,会返回数据给到我方以及第三方会请求我们的回调接口,那么就需要对第三方返回的参数进行验签,而验签同理需要将加密参数提取出来,并进行排序,需要明白的是,此时解签我们需要用到 第三方的公钥 进行验签,

5.其中需要注意的是:我所了解的目前golang 加密和解密一般都是用的pem证书,而大部分第三方提供的证书是 .pfx .cer ,所以我需要在win上安装openssl工具,然后将 .pfx .cer转成 .pem证书

证书转换方式如下:

复制代码
1. 下载Win64OpenSSL-3_1_2.exe 并安装(下载:https://slproweb.com/products/Win32OpenSSL.html)
2. 环境变量 Path  加入 D:\OpenSSL-Win64\bin
3. amd 下命令: openssl version   验证是否安装成功

4. 通过命令解析生成 公私钥 uat.pfx 
  openssl pkcs12 -in xxxx.pfx -nodes -out server.pem  #生成为原生格式pem 私钥 ******
  openssl rsa -in server.pem -out server.key          #生成为rsa格式私钥文件
  openssl x509 -in server.pem  -out server.crt
  openssl pkcs12 -in xxxx.pfx -clcerts -nokeys -out key.cert
  openssl x509 -inform der -in xxx.cer -out xxx.pem    #公钥pem *******

二、加密和解密要用到的方法(供参考)

1.参数进行排序并连接成 key1=value1&key2=value2 字符串并将sign参数排除在外

Go 复制代码
func GetSignStr(maps map[string]string) string {
	signData := make(map[string]string, 0)
	for k, v := range maps {
		if k != "sign" {
			signData[k] = v
		}
	}

	var dataParams string
	var keys []string
	for k := range signData {
		keys = append(keys, k)
	}
	sort.Strings(keys)
	for _, k := range keys {
		dataParams = dataParams + k + "=" + signData[k] + "&"
	}
	ff := dataParams[0 : len(dataParams)-1]
	return ff
}

2.将排序好的参数字符串进行证书私钥加密并将加密串base64转换

Go 复制代码
// 将排序好的参数进行加密签名并转成base64字符串返回 signData:参数字符串 pemPath :私钥路径./aaa/bbb/private.pem
func PrivateSha1SignData(signData, pemPath string) (string, error) {
	h := crypto.Hash.New(crypto.SHA1)
	h.Write([]byte(signData))
	hashed := h.Sum(nil)
	_, private, err := LoadPrivateKey(pemPath)
	if err != nil {
		return "", err
	}
	signer, err := rsa.SignPKCS1v15(rand.Reader, private, crypto.SHA1, hashed)
	if err != nil {
		fmt.Println("PrivateSha1SignData Error  from signing: %s\n", err)
		return "", err
	}
	return Base64Encode(signer), nil
}

// 根据路径加载证书文件
func LoadPrivateKey(pemPath string) (string, *rsa.PrivateKey, error) {
	key, err := ioutil.ReadFile(pemPath)
	if err != nil {
		return "", nil, err
	}
	block, _ := pem.Decode(key)
	if block == nil {
		return "", nil, errors.New("pem.Decode err")
	}
	p, err := x509.ParsePKCS8PrivateKey(block.Bytes)
	if err != nil {
		return "", nil, err
	} else {
		pk := p.(*rsa.PrivateKey)
		return "", pk, nil
	}
}

// base64 加密
func Base64Encode(data []byte) string {
	return base64.StdEncoding.EncodeToString(data)
}

// base64 解密
func Base64Decode(data string) ([]byte, error) {
	return base64.StdEncoding.DecodeString(data)
}

3.将sign和加密参数转成map post方式请求第三方接口,而此处需要注意接口返回的参数,返回的参数sign值中是带有+ 和 / 符号的,按正常情况 用 url.QueryUnescape(string(body:))转换是安全的,但奇怪的是本人遇到过用此方法反而字符串中出现了空格,所以用string(body:)就OK了

Go 复制代码
func PayPost(requrl string, request map[string]string, publicPemPath string) (respData string, err error) {
	http := TimeoutClient()
	resp, err := http.Post(requrl, "application/x-www-form-urlencoded", strings.NewReader(HttpBuildQuery(request)))

	if err != nil {
		return respData, errors.New("paypost err1:" + err.Error())
	}
	if resp.StatusCode != 200 {
		return respData, fmt.Errorf("http request response StatusCode:%v", resp.StatusCode)
	}
	defer resp.Body.Close()
	data, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return respData, errors.New("paypost err2:" + err.Error())
	}


	dataString := string(data[:])
    //dataString, err := url.QueryUnescape(string(data[:]))
	//if err != nil {
	//	return respData, errors.New("paypost err3:" + err.Error())
	//}
	
	return respData, err
}
  1. 对方回调我们方接口接收参数
Go 复制代码
//获取参数字节码
    body, err := ioutil.ReadAll(c.Request.Body)
	info := ""
	if err != nil {
		//返回错误
        return
	}
	//参数转成字符串
	dataString, err := url.QueryUnescape(string(body[:]))
	if err != nil {
		//返回错误
		return
	}

5.解密

Go 复制代码
// signStr:参数字符k=v&k=v
// signature:加密串base64需要转换
// PublicPemPath:第三方公约路径 ./aaa/bbb/public.pem
// 解密      
func YSCallbackVerify(signStr,signature, PublicPemPath string) (bool, error) {
	
	hash := crypto.Hash.New(crypto.SHA1)
	hash.Write([]byte(str))
	hashed := hash.Sum(nil)

	var inSign []byte
	inSign, err := Base64Decode(signature)
	if err != nil {
		return false, errors.New("解析signature失败:" + err.Error())
	}
	publicPem, err := LoadPublicKey(PublicPemPath)
	if err != nil {
		return false, errors.New("获取公钥失败:" + err.Error())
	}

	err = rsa.VerifyPKCS1v15(publicPem, crypto.SHA1, hashed, inSign)
	if err != nil {
		return false, errors.New("PublicSha1Verify Error from signing:" + err.Error())
	}
	return true, nil
}

//加载公约证书
func LoadPublicKey(pemPath string) (*rsa.PublicKey, error) {
	key, err := ioutil.ReadFile(pemPath)
	if err != nil {
		return nil, errors.New("加载公钥错误1:" + err.Error())
	}
	block, _ := pem.Decode(key)
	if block == nil {
		return nil, errors.New("加载公钥错误2:" + err.Error())
	}

	certBody, err := x509.ParseCertificate(block.Bytes)
	if err != nil {
		return nil, errors.New("加载公钥错误3:" + err.Error())
	}
	pb := certBody.PublicKey.(*rsa.PublicKey)
	return pb, nil
}

6.说明:请求第三方接口,一般参数会有两层或更多层,那么我们获取到参数而第二层原本是json字符串,那么就需要注意,我们golang定义的结构体struct各字段就需要跟它对应,以免参数书序不对造成解密失败。当然,如上方法都是用的map接收参数当中需要将struct转成map 将map转成struct则需要根据需要来灵活转换。

相关推荐
Rust研习社2 小时前
组合真的优于继承吗?为什么 Rust 和 Go 都拥抱组合舍弃继承?
后端·rust·编程语言
IT_陈寒2 小时前
JavaScript的闭包把我坑惨了,说好的内存会自动回收呢?
前端·人工智能·后端
CaffeinePro3 小时前
Pydantic深度使用:数据校验、枚举、ORM映射
后端·fastapi
Chenyiax3 小时前
从 Chat 到 Responses:OpenAI API 抽象为什么变了?
后端
MariaH3 小时前
Koa和Express的区别
后端
MariaH3 小时前
Koa框架的使用
后端
luckdewei4 小时前
那个用 passlib 做认证的新同事,上线第一天就把用户密码写进了日志
后端
ping某6 小时前
为什么 Nginx 明明监听了 80,转发后端时却用了 4xxxx 端口?
后端·nginx
JustHappy6 小时前
我汇总了身边朋友的经历才发现,其实第一份实习是最难找的......
前端·后端·面试
uhakadotcom6 小时前
在python 的 工程化架构中 ,什么是 薄包装器层?
后端·面试·github