Certificate and decrypting the callback message

Update Time:2024.09.18

To ensure security, WeChat Pay uses AES-256-GCM encryption for key information in the callback notification and platform certificate download API. This chapter details the format of encrypted messages and how to decrypt them.


1. Format of Encrypted Messages

AES-AES-GCM is an Authenticated Encryption algorithm of the NIST standard and an encryption mode that can guarantee the confidentiality, integrity and authenticity of data.It is most widely used in TLS.

The encryption key used in the certificate and callback messages is the API V3 key.

We use a separate JSON object to represent encrypted data. For ease of reading, the example is Pretty formatted and comments are added.

1{
2	"original_type": "transaction", //Object type before encryption
3	"algorithm": "AEAD_AES_256_GCM", // Encryption algorithm
4	// Base64 encoded ciphertext
5	"ciphertext": "...",
6	// Random string initialization vector used for encryption
7	"nonce": "...",
8	// Additional data package (can be left blank)
9	"associated_data": ""
10}
11

Notice

The encrypted random string is irrelevant with and different from the random string used when signing.

2. Decryption

Please refer to RFC 5116 for details of the algorithm API.

Most programming languages (newer versions) support AEAD_AES_256_GCM. Developers can refer to the following examples to learn how to use your programming language for decryption.

JAVA

1package com.wechat.v3;
2
3import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
4import com.fasterxml.jackson.annotation.JsonProperty;
5import com.wechat.pay.contrib.apache.httpclient.exception.ParseException;
6import com.wechat.pay.contrib.apache.httpclient.util.AesUtil;
7
8import java.nio.charset.StandardCharsets;
9import java.security.GeneralSecurityException;
10
11
12class DecodeCipher {
13
14    private static final String apiV3Key = "";
15
16    private void setDecryptData(Notification notification) throws ParseException {
17
18        Notification.Resource resource = notification.getResource();
19        String getAssociateddData = "";
20        if (resource.getAssociatedData() != null) {
21            getAssociateddData = resource.getAssociatedData();
22        }
23        byte[] associatedData = getAssociateddData.getBytes(StandardCharsets.UTF_8);
24        byte[] nonce = resource.getNonce().getBytes(StandardCharsets.UTF_8);
25        String ciphertext = resource.getCiphertext();
26        AesUtil aesUtil = new AesUtil(apiV3Key.getBytes(StandardCharsets.UTF_8));
27        String decryptData;
28        try {
29            decryptData = aesUtil.decryptToString(associatedData, nonce, ciphertext);
30        } catch (GeneralSecurityException e) {
31            throw new ParseException("AES解密失败,resource:" + resource.toString(), e);
32        }
33        notification.setDecryptData(decryptData);
34    }
35
36
37    @JsonIgnoreProperties(ignoreUnknown = true)
38    public class Notification {
39
40        @JsonProperty("id")
41        private String id;
42        @JsonProperty("create_time")
43        private String createTime;
44        @JsonProperty("event_type")
45        private String eventType;
46        @JsonProperty("resource_type")
47        private String resourceType;
48        @JsonProperty("summary")
49        private String summary;
50        @JsonProperty("resource")
51        private Resource resource;
52        private String decryptData;
53
54        @Override
55        public String toString() {
56            return "Notification{" +
57                    "id='" + id + '\'' +
58                    ", createTime='" + createTime + '\'' +
59                    ", eventType='" + eventType + '\'' +
60                    ", resourceType='" + resourceType + '\'' +
61                    ", decryptData='" + decryptData + '\'' +
62                    ", summary='" + summary + '\'' +
63                    ", resource=" + resource +
64                    '}';
65        }
66
67        public String getId() {
68            return id;
69        }
70
71        public String getCreateTime() {
72            return createTime;
73        }
74
75        public String getEventType() {
76            return eventType;
77        }
78
79        public String getDecryptData() {
80            return decryptData;
81        }
82
83        public String getSummary() {
84            return summary;
85        }
86
87        public String getResourceType() {
88            return resourceType;
89        }
90
91        public Resource getResource() {
92            return resource;
93        }
94
95        public void setDecryptData(String decryptData) {
96            this.decryptData = decryptData;
97        }
98
99        @JsonIgnoreProperties(ignoreUnknown = true)
100        public class Resource {
101
102            @JsonProperty("algorithm")
103            private String algorithm;
104            @JsonProperty("ciphertext")
105            private String ciphertext;
106            @JsonProperty("associated_data")
107            private String associatedData;
108            @JsonProperty("nonce")
109            private String nonce;
110            @JsonProperty("original_type")
111            private String originalType;
112
113            public String getAlgorithm() {
114                return algorithm;
115            }
116
117            public String getCiphertext() {
118                return ciphertext;
119            }
120
121            public String getAssociatedData() {
122                return associatedData;
123            }
124
125            public String getNonce() {
126                return nonce;
127            }
128
129            public String getOriginalType() {
130                return originalType;
131            }
132
133            @Override
134            public String toString() {
135                return "Resource{" +
136                        "algorithm='" + algorithm + '\'' +
137                        ", ciphertext='" + ciphertext + '\'' +
138                        ", associatedData='" + associatedData + '\'' +
139                        ", nonce='" + nonce + '\'' +
140                        ", originalType='" + originalType + '\'' +
141                        '}';
142            }
143        }
144
145    }
146}

PHP

1<?php
2require_once('vendor/autoload.php');
3
4use WeChatPay\Crypto\Rsa;
5use WeChatPay\Crypto\AesGcm;
6use WeChatPay\Formatter;
7
8$inWechatpaySignature = '';// Get this value from the header in response
9$inWechatpayTimestamp = '';// Get this value from the header in response
10$inWechatpaySerial = '';// Get this value from the header in response
11$inWechatpayNonce = '';// Get this value from the header in response
12$inBody = '';// Get this value from the body in response
13$apiv3Key = '';
14$platformPublicKeyInstance = Rsa::from('file:///path/to/wechatpay/inWechatpaySerial.pem', Rsa::KEY_TYPE_PUBLIC);
15
16$timeOffsetStatus = 300 >= abs(Formatter::timestamp() - (int)$inWechatpayTimestamp);
17$verifiedStatus = Rsa::verify(
18    Formatter::joinedByLineFeed($inWechatpayTimestamp, $inWechatpayNonce, $inBody),
19    $inWechatpaySignature,
20    $platformPublicKeyInstance
21);
22if ($timeOffsetStatus && $verifiedStatus) {
23    $inBodyArray = (array)json_decode($inBody, true);
24    ['resource' => [
25        'ciphertext'      => $ciphertext,
26        'nonce'           => $nonce,
27        'associated_data' => $aad
28    ]] = $inBodyArray;
29    $inBodyResource = AesGcm::decrypt($ciphertext, $apiv3Key, $nonce, $aad);
30    $inBodyResourceArray = (array)json_decode($inBodyResource, true);
31}

GO

1package wechatpay
2
3import (
4	"crypto/aes"
5	"crypto/cipher"
6	"encoding/base64"
7)
8
9// DecryptAES256GCM 使用 AEAD_AES_256_GCM 算法进行解密
10//
11// 你可以使用此算法完成微信支付平台证书和回调报文解密,详见:
12// https://wechatpay-api.gitbook.io/wechatpay-api-v3/qian-ming-zhi-nan-1/zheng-shu-he-hui-tiao-bao-wen-jie-mi
13func DecryptAES256GCM(aesKey, associatedData, nonce, ciphertext string) (plaintext string, err error) {
14	decodedCiphertext, err := base64.StdEncoding.DecodeString(ciphertext)
15	if err != nil {
16		return "", err
17	}
18	c, err := aes.NewCipher([]byte(aesKey))
19	if err != nil {
20		return "", err
21	}
22	gcm, err := cipher.NewGCM(c)
23	if err != nil {
24		return "", err
25	}
26	dataBytes, err := gcm.Open(nil, []byte(nonce), decodedCiphertext, []byte(associatedData))
27	if err != nil {
28		return "", err
29	}
30	return string(dataBytes), nil
31}

 

About  WeChat  Pay

Powered By Tencent & Tenpay Copyright©

2005-2025 Tenpay All Rights Reserved.

Contact Us
Wechat Pay Global

WeChat Pay Global

Contact Us

Customer Service Tel

+86 571 95017

9:00-18:00 Monday-Friday GMT+8

Business Development

wxpayglobal@tencent.com

Developer Support

wepayTS@tencent.com

Wechat Pay Global

About Tenpay
Powered By Tencent & Tenpay Copyright© 2005-2025 Tenpay All Rights Reserved.