微信支付V3之签名、验签、加密、解密

微信支付APIv3之签名、验签、加密、解密——Golang

最近公司的项目是微信小程序的开发,使用了微信支付APIv3的接口。虽然只是接口的调用,但是里面的签名验签和加密解密部分是我在工作中头一次遇到(因为我刚工作嘛),折腾了一阵终于搞好了,但对于里面的原理和各种细节仍然很多不懂。

如题所示,本文主要包括调用接口时的签名和对敏感信息的加密,收到回复时的验签和对敏感信息的解密。
微信支付APIv3的文档: https://wechatpay-api.gitbook.io/wechatpay-api-v3/
在线加解密网站: https://www.keylala.cn/aes

1. 签名

Authorization, err := GetAuth(method, url, body)
//请求头
headers := map[string]string{
	"Content-Type":     "application/json",
	"Accept":           "application/json",
	"User-Agent":       "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.1 (KHTML, like Gecko) Chrome/14.0.835.163 Safari/535.1",
	"Authorization":    Authorization,
	"Wechatpay-Serial": 平台证书序列号,
	}
func GetAuth(method, url, body string) (res string, err error) {
	Authorization := "WECHATPAY2-SHA256-RSA2048" //固定字符串
	mchid := conf.AppConf.Mchid //服务商商户号

	nonce_str := strings.ToUpper(stringutils.GetRandomString(32, "")) //32位随机字符串
	timestamp := strconv.FormatInt(time.Now().Unix(), 10) //时间戳
	signature, err := Sign(method, url, timestamp, body, nonce_str)//生成签名

	serial_no := conf.AppConf.MchidSerialNo //取自配置 商户证书序列号
	Authorization = fmt.Sprintf(`%s mchid="%s",nonce_str="%s",signature="%s",timestamp="%s",serial_no="%s"`,
		Authorization, mchid, nonce_str, signature, timestamp, serial_no)

	return Authorization, err

}

func Sign(method, url, timestamp, body, nonce_str string) (res string, err error) {
	//组装被加密的字符串
	randomStr := nonce_str
	targetStr := method + "\n" + url + "\n" + timestamp + "\n" + randomStr + "\n" + body + "\n"
	//加密
	sign, err := SHA256WithRsaBase64(targetStr)

	return sign, err
}

func SHA256WithRsaBase64(origData string) (sign string, err error) {

	var keypath = "商户证书私钥地址"
	key, err := ioutil.ReadFile(keypath)

	blocks, _ := pem.Decode(key)
	if blocks == nil || blocks.Type != "PRIVATE KEY" {
		glog.Println("failed to decode PRIVATE KEY")
		return

	}
	privateKey, err := x509.ParsePKCS8PrivateKey(blocks.Bytes)

	h := sha256.New()
	h.Write([]byte(origData))
	digest := h.Sum(nil)
	s, _ := rsa.SignPKCS1v15(nil, privateKey.(*rsa.PrivateKey), crypto.SHA256, digest)
	sign = base64.StdEncoding.EncodeToString(s)

	return sign, err
}

2. 验签

//取得回复后调用此方法进行签名验证
func CheckSign(sign map[string]string) (bool, error) {
	time := sign["timestamp"]
	nonce := sign["nonce"]
	signature := sign["signature"]
	body := sign["body"]
	wxSerial := sign["wxSerial"]

	//验签之前需要先验证平台证书序列号是否正确一致
	//此处部分代码省略
	if cert.SerialNo != wxSerial {
		glog.Error("证书号错误或已过期")
		return false, err
	}
	checkStr := time + "\n" + nonce + "\n" + body + "\n"

	var keypath = "微信平台证书公钥路径"
	key, err := ioutil.ReadFile(keypath)

	blocks, _ := pem.Decode(key)
	if blocks == nil || blocks.Type != "PUBLIC KEY" {
		glog.Println("failed to decode PUBLIC KEY")
		return false, err
	}
	oldSign, err := base64.StdEncoding.DecodeString(signature)
	pub, err := x509.ParsePKIXPublicKey(blocks.Bytes)
	hashed := sha256.Sum256([]byte(checkStr))
	err = rsa.VerifyPKCS1v15(pub.(*rsa.PublicKey), crypto.SHA256, hashed[:], oldSign)

	return true, err
}

3. 加密

/ 加密
func RsaEncrypt(origData []byte) (string, error) {
	publicKey := []byte(`平台证书公钥`)
	block, _ := pem.Decode(publicKey)
	if block == nil {
		return "", errors.New("public key error")
	}
	pubInterface, err := x509.ParsePKIXPublicKey(block.Bytes)
	secretMessage := origData
	rng := rand.Reader

	cipherdata, err := rsa.EncryptOAEP(sha1.New(), rng, pubInterface.(*rsa.PublicKey), secretMessage, nil)
	if err != nil {
		fmt.Fprintf(os.Stderr, "Error from encryption: %s\n", err)
	}

	ciphertext := base64.StdEncoding.EncodeToString(cipherdata)
	fmt.Printf("Ciphertext: %x\n", ciphertext)
	return ciphertext, err

}

4. AES-256-GCM解密

func RsaDecrypt(ciphertext, nonce2, associatedData2 string) (plaintext string, err error) {
	key := []byte(conf.AppConf.ApiV3Key) //key是APIv3密钥,长度32位,由管理员在商户平台上自行设置的
	additionalData := []byte(associatedData2) 
	nonce := []byte(nonce2)                   

	block, err := aes.NewCipher(key)
	aesgcm, err := cipher.NewGCMWithNonceSize(block, len(nonce))
	cipherdata, _ := base64.StdEncoding.DecodeString(ciphertext)
	plaindata, err := aesgcm.Open(nil, nonce, cipherdata, additionalData)
	fmt.Println("plaintext: ", string(plaindata))

	return string(plaindata), err
}

5. AES - ECB解密

func AesDecrypt(ciphertext string) string {
	key := []byte(conf.AppConf.MinKey) // 加密的密钥
	encrypted, err := base64.StdEncoding.DecodeString(ciphertext)
	if err != nil {
		fmt.Println(err)
		return ""
	}

	genKey := make([]byte, 16)
	copy(genKey, key)
	for i := 16; i < len(key); {
		for j := 0; j < 16 && i < len(key); j, i = j+1, i+1 {
			genKey[j] ^= key[i]
		}
	}

	cipher, _ := aes.NewCipher(genKey)
	decrypted := make([]byte, len(encrypted))

	for bs, be := 0, cipher.BlockSize(); bs < len(encrypted); bs, be = bs+cipher.BlockSize(), be+cipher.BlockSize() {
		cipher.Decrypt(decrypted[bs:be], encrypted[bs:be])
	}

	trim := 0
	if len(decrypted) > 0 {
		trim = len(decrypted) - int(decrypted[len(decrypted)-1])
	}

	decrypted = decrypted[:trim]

	log.Println("解密结果:", string(decrypted))
	return string(decrypted)
}
  • 1
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值