最近公司的项目是微信小程序的开发,使用了微信支付APIv3的接口。虽然只是接口的调用,但是里面的签名验签和加密解密部分是我在工作中头一次遇到(因为我刚工作嘛),折腾了一阵终于搞好了,但对于里面的原理和各种细节仍然很多不懂。
如题所示,本文主要包括调用接口时的签名和对敏感信息的加密,收到回复时的验签和对敏感信息的解密。
微信支付APIv3的文档: https://wechatpay-api.gitbook.io/wechatpay-api-v3/
在线加解密网站: https://www.keylala.cn/aes
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
}
//取得回复后调用此方法进行签名验证
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
}
/ 加密
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
}
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
}
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)
}