Login expired. Please log in again.

Feedback

0/300

Feedback

Submitted successfully

ok

Feedback

Network exception, please try again later

ok

证书和回调报文解密

为了保证安全性,微信支付在回调通知和平台证书下载接口中,对关键信息进行了AES-256-GCM加密。本章节详细介绍了加密报文的格式,以及如何进行解密。

1. 加密报文格式

AES-GCM是一种NIST标准的认证加密算法, 是一种能够同时保证数据的保密性、 完整性和真实性的一种加密模式。它最广泛的应用是在TLS中。

证书和回调报文使用的加密密钥为APIv3密钥。

对于加密的数据,我们使用了一个独立的JSON对象来表示。为了方便阅读,示例做了Pretty格式化,并加入了注释。

{
	"original_type": "transaction", // 加密前的对象类型
	"algorithm": "AEAD_AES_256_GCM", // 加密算法

	// Base64编码后的密文
	"ciphertext": "...",
	// 加密使用的随机串初始化向量)
	"nonce": "...",
	// 附加数据包(可能为空)
	"associated_data": ""
}
注意
加密的随机串,跟签名时使用的随机串没有任何关系,是不一样的。

2. 解密

算法接口的细节,可以参考RFC 5116。

大部分编程语言(较新版本)都支持了AEAD_AES_256_GCM 。开发者可以参考下列的示例,了解如何使用您的编程语言实现解密。

package com.wechat.v3;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.wechat.pay.contrib.apache.httpclient.exception.ParseException;
import com.wechat.pay.contrib.apache.httpclient.util.AesUtil;

import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;


class DecodeCipher {

    private static final String apiV3Key = "";

    private void setDecryptData(Notification notification) throws ParseException {

        Notification.Resource resource = notification.getResource();
        String getAssociateddData = "";
        if (resource.getAssociatedData() != null) {
            getAssociateddData = resource.getAssociatedData();
        }
        byte[] associatedData = getAssociateddData.getBytes(StandardCharsets.UTF_8);
        byte[] nonce = resource.getNonce().getBytes(StandardCharsets.UTF_8);
        String ciphertext = resource.getCiphertext();
        AesUtil aesUtil = new AesUtil(apiV3Key.getBytes(StandardCharsets.UTF_8));
        String decryptData;
        try {
            decryptData = aesUtil.decryptToString(associatedData, nonce, ciphertext);
        } catch (GeneralSecurityException e) {
            throw new ParseException("AES解密失败,resource:" + resource.toString(), e);
        }
        notification.setDecryptData(decryptData);
    }


    @JsonIgnoreProperties(ignoreUnknown = true)
    public class Notification {

        @JsonProperty("id")
        private String id;
        @JsonProperty("create_time")
        private String createTime;
        @JsonProperty("event_type")
        private String eventType;
        @JsonProperty("resource_type")
        private String resourceType;
        @JsonProperty("summary")
        private String summary;
        @JsonProperty("resource")
        private Resource resource;
        private String decryptData;

        @Override
        public String toString() {
            return "Notification{" +
                    "id='" + id + '\'' +
                    ", createTime='" + createTime + '\'' +
                    ", eventType='" + eventType + '\'' +
                    ", resourceType='" + resourceType + '\'' +
                    ", decryptData='" + decryptData + '\'' +
                    ", summary='" + summary + '\'' +
                    ", resource=" + resource +
                    '}';
        }

        public String getId() {
            return id;
        }

        public String getCreateTime() {
            return createTime;
        }

        public String getEventType() {
            return eventType;
        }

        public String getDecryptData() {
            return decryptData;
        }

        public String getSummary() {
            return summary;
        }

        public String getResourceType() {
            return resourceType;
        }

        public Resource getResource() {
            return resource;
        }

        public void setDecryptData(String decryptData) {
            this.decryptData = decryptData;
        }

        @JsonIgnoreProperties(ignoreUnknown = true)
        public class Resource {

            @JsonProperty("algorithm")
            private String algorithm;
            @JsonProperty("ciphertext")
            private String ciphertext;
            @JsonProperty("associated_data")
            private String associatedData;
            @JsonProperty("nonce")
            private String nonce;
            @JsonProperty("original_type")
            private String originalType;

            public String getAlgorithm() {
                return algorithm;
            }

            public String getCiphertext() {
                return ciphertext;
            }

            public String getAssociatedData() {
                return associatedData;
            }

            public String getNonce() {
                return nonce;
            }

            public String getOriginalType() {
                return originalType;
            }

            @Override
            public String toString() {
                return "Resource{" +
                        "algorithm='" + algorithm + '\'' +
                        ", ciphertext='" + ciphertext + '\'' +
                        ", associatedData='" + associatedData + '\'' +
                        ", nonce='" + nonce + '\'' +
                        ", originalType='" + originalType + '\'' +
                        '}';
            }
        }

    }
}
<?php
require_once('vendor/autoload.php');

use WeChatPay\Crypto\Rsa;
use WeChatPay\Crypto\AesGcm;
use WeChatPay\Formatter;

$inWechatpaySignature = '';// Get this value from the header in response
$inWechatpayTimestamp = '';// Get this value from the header in response
$inWechatpaySerial = '';// Get this value from the header in response
$inWechatpayNonce = '';// Get this value from the header in response
$inBody = '';// Get this value from the body in response
$apiv3Key = '';
$platformPublicKeyInstance = Rsa::from('file:///path/to/wechatpay/inWechatpaySerial.pem', Rsa::KEY_TYPE_PUBLIC);

$timeOffsetStatus = 300 >= abs(Formatter::timestamp() - (int)$inWechatpayTimestamp);
$verifiedStatus = Rsa::verify(
    Formatter::joinedByLineFeed($inWechatpayTimestamp, $inWechatpayNonce, $inBody),
    $inWechatpaySignature,
    $platformPublicKeyInstance
);
if ($timeOffsetStatus && $verifiedStatus) {
    $inBodyArray = (array)json_decode($inBody, true);
    ['resource' => [
        'ciphertext'      => $ciphertext,
        'nonce'           => $nonce,
        'associated_data' => $aad
    ]] = $inBodyArray;
    $inBodyResource = AesGcm::decrypt($ciphertext, $apiv3Key, $nonce, $aad);
    $inBodyResourceArray = (array)json_decode($inBodyResource, true);
}
package wechatpay

import (
	"crypto/aes"
	"crypto/cipher"
	"encoding/base64"
)

// DecryptAES256GCM 使用 AEAD_AES_256_GCM 算法进行解密
//
// 你可以使用此算法完成微信支付平台证书和回调报文解密,详见:
// https://wechatpay-api.gitbook.io/wechatpay-api-v3/qian-ming-zhi-nan-1/zheng-shu-he-hui-tiao-bao-wen-jie-mi
func DecryptAES256GCM(aesKey, associatedData, nonce, ciphertext string) (plaintext string, err error) {
	decodedCiphertext, err := base64.StdEncoding.DecodeString(ciphertext)
	if err != nil {
		return "", err
	}
	c, err := aes.NewCipher([]byte(aesKey))
	if err != nil {
		return "", err
	}
	gcm, err := cipher.NewGCM(c)
	if err != nil {
		return "", err
	}
	dataBytes, err := gcm.Open(nil, []byte(nonce), decodedCiphertext, []byte(associatedData))
	if err != nil {
		return "", err
	}
	return string(dataBytes), nil
}
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
import base64

def decrypt(nonce, ciphertext, associated_data):
    key = "Your32Apiv3Key"

    key_bytes = str.encode(key)
    nonce_bytes = str.encode(nonce)
    ad_bytes = str.encode(associated_data)
    data = base64.b64decode(ciphertext)

    aesgcm = AESGCM(key_bytes)
    return aesgcm.decrypt(nonce_bytes, data, ad_bytes)
    页面导航

About  WeChat  Pay

Powered By Tencent & Tenpay Copyright©

2005-2024 Tenpay All Rights Reserved.

Contact Us
Wechat Pay Global

WeChat Pay Global

置顶