Signature Generation

Update Time:2025.01.06

Merchants can follow the steps below to generate the requested signature. At the end of this section, we have prepared demo codes in a variety of commonly used programming languages for developers' reference.。


WeChat Pay API V3 requires merchants to sign the request. WeChat Pay will verify the signature after receiving the request. If signature verification fails, WeChat Pay API V3 will refuse to process the request and return 401 Unauthorized.

1. Preparation

A merchant needs to obtain a WeChat Pay merchant account, log in to the merchant platform through the super administrator account, and obtain the Merchant API Certificate . The compressed package of Merchant API Certificate contains the private key and merchant certificate necessary for signing.

2. Constructing a Signature String

Constructing a Signature String We hope that the merchant's technical developers will construct the signature string according to the conventions specified in the current document. WeChat Pay uses the same method to construct the signature string. The signature will not be verified if the merchant constructs the signature string incorrectly. The specific format of the signature string is explained below.

A signature string has five lines, and each line has a parameter. Each line ends with \n (Newline character; the ASCII code value is 0x0A), and even the last line shall end with \n. If the parameter itself ends with \n, an additional \n is also required.

1HTTP request method\n
2URL\n
3Request timestamp\n
4Request random string\n
5Request body\n

By calling the Obtaining WeChat Pay Platform Certificate API in the command line, we will provide developers with step by step instructions on how to request signatures. According to the API document, the URL for obtaining the merchant platform certificate is https://apihk.mch.weixin.qq.com/v3/global/certificates. The request method is GET with no query parameters.

Step 1: Obtain the HTTP request method (such as GET, POST, and PUT).

1GET

Step 2: Obtain the absolute URL of the request, and remove the domain to obtain the URL participating in the signature. If the request has query parameters, a '?' and the corresponding query string should be added to the end of the URL.

1/v3/global/certificates

Step 3: Obtain the current timestamp of the system when the request was initiated. Timestamps include the total number of seconds from January 1, 1970, 00: 00: 00, GMT (Beijing time, January 1, 1970, 08: 00: 00) to the present time. WeChat Pay will refuse to process requests initiated a long time ago. Merchants are requested to keep their system time accurate.

1 $ date +%s 
2 1554208460

Step 4: Generate a random string request, referring to Algorithm for Generating Random Numbers.Here, we use the command line to directly generate a random string request.

1$ hexdump -n 16 -e '4/4 "%08X" 1 "\n"' /dev/random 
2 593BEC0C930BF1AFEB40B4A08C8FB242

Step 5: Obtain the request body in the request.

  • When the request method is GET, the message body is blank.

  • When the request method is POST or PUT, use the actual JSON message sent.

  • For an image upload API, use the JSON message corresponding to meta.

For a certificate download API, the request body is an empty string.

Step 6: According to the aforementioned rules, the constructed request signature string is:

1GET\n 
2 /v3/global/certificates\n 
3 1554208460\n 
4 593BEC0C930BF1AFEB40B4A08C8FB242\n 
5 \n

3. Calculating Signature Value

The signature functions provided by most programming languages support signing signature data. It is recommended that merchants call such functions, use the merchant's private key to sign the signature string with SHA256 with RSA, and perform Base64 encoding on the signature result to obtain the signature value.

1$ echo -n -e \
2 "GET\n/v3/global/certificates\n1554208460\n593BEC0C930BF1AFEB40B4A08C8FB242\n\n" \
3 | openssl dgst -sha256 -sign apiclient_key.pem \
4 | openssl base64 -A
5 uOVRnA4qG/MNnYzdQxJanN+zU+lTgIcnU9BxGw5dKjK+VdEUz2FeIoC+D5sB/LN+nGzX3hfZg6r5wT1pl2ZobmIc6p0ldN7J6yDgUzbX8Uk3sD4a4eZVPTBvqNDoUqcYMlZ9uuDdCvNv4TM3c1WzsXUrExwVkI1XO5jCNbgDJ25nkT/c1gIFvqoogl7MdSFGc4W4xZsqCItnqbypR3RuGIlR9h9vlRsy7zJR9PBI83X8alLDIfR1ukt1P7tMnmogZ0cuDY8cZsd8ZlCgLadmvej58SLsIkVxFJ8XyUgx9FmutKSYTmYtWBZ0+tNvfGmbXU7cob8H/4nLBiCwIUFluw==

4. Setting the HTTP Header

WeChat Pay Merchant API V3 requires the request to pass the signature through the HTTP Authorization header. Authorization is composed of the authentication type and signature information.

Here we use command lines to demonstrate how to generate a signature.

1Authorization: authentication type and signature information
2

Authorization includes:

1.Authentication type, currently WECHATPAY2-SHA256-RSA2048

2.Signature information

  • This is the merchant ID mchid of the merchant that initiated the request (including directly connected merchants, service providers, or channel merchants)

  • Request random string nonce_str

  • Timestamp timestamp

  • Signature value signature

Notice

There is no requirement on the order for the five pieces of signature information above.

An example of the Authorization header is as follows: (note that the example may have line breaks due to typesetting. The actual data should be on one line.)

1Authorization: WECHATPAY2-SHA256-RSA2048 mchid="1900009191",nonce_str="593BEC0C930BF1AFEB40B4A08C8FB242",signature="uOVRnA4qG/MNnYzdQxJanN+zU+lTgIcnU9BxGw5dKjK+VdEUz2FeIoC+D5sB/LN+nGzX3hfZg6r5wT1pl2ZobmIc6p0ldN7J6yDgUzbX8Uk3sD4a4eZVPTBvqNDoUqcYMlZ9uuDdCvNv4TM3c1WzsXUrExwVkI1XO5jCNbgDJ25nkT/c1gIFvqoogl7MdSFGc4W4xZsqCItnqbypR3RuGIlR9h9vlRsy7zJR9PBI83X8alLDIfR1ukt1P7tMnmogZ0cuDY8cZsd8ZlCgLadmvej58SLsIkVxFJ8XyUgx9FmutKSYTmYtWBZ0+tNvfGmbXU7cob8H/4nLBiCwIUFluw==",timestamp="1554208460",serial_no="1DDE55AD98ED71D6EDD4A4A16996DE7B47773A8C"

Finally, we can create an HTTP request that includes a signature.

1$ curl https://apihk.mch.weixin.qq.com/v3/global/certificates -H 'Authorization: WECHATPAY2-SHA256-RSA2048 mchid="1900009191",nonce_str="593BEC0C930BF1AFEB40B4A08C8FB242",signature="uOVRnA4qG/MNnYzdQxJanN+zU+lTgIcnU9BxGw5dKjK+VdEUz2FeIoC+D5sB/LN+nGzX3hfZg6r5wT1pl2ZobmIc6p0ldN7J6yDgUzbX8Uk3sD4a4eZVPTBvqNDoUqcYMlZ9uuDdCvNv4TM3c1WzsXUrExwVkI1XO5jCNbgDJ25nkT/c1gIFvqoogl7MdSFGc4W4xZsqCItnqbypR3RuGIlR9h9vlRsy7zJR9PBI83X8alLDIfR1ukt1P7tMnmogZ0cuDY8cZsd8ZlCgLadmvej58SLsIkVxFJ8XyUgx9FmutKSYTmYtWBZ0+tNvfGmbXU7cob8H/4nLBiCwIUFluw==",timestamp="1554208460",serial_no="1DDE55AD98ED71D6EDD4A4A16996DE7B47773A8C"'

5. Demo Code

Developers can visit Development Tool to obtain the corresponding language library.Please refer to FAQs on how to load the private key in the program.

The sample code for calculating the signature is as follows.

JAVA

1import okhttp3.HttpUrl;
2package com.wechat.v3;
3
4import org.apache.http.client.methods.HttpGet;
5import org.apache.http.client.methods.HttpRequestBase;
6import org.junit.Test;
7
8import java.io.IOException;
9import java.net.URI;
10import java.nio.charset.StandardCharsets;
11import java.security.*;
12import java.util.Base64;
13
14public class SignTest {
15protected static final String SYMBOLS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
16protected static final SecureRandom RANDOM = new SecureRandom();
17protected static final PrivateKey privateKey = null; //need to be initialized
18protected static final String certificateSerialNumber = null; //need to be initialized
19protected static final String merchantId = null; //need to be initialized
20
21@Test
22public void signTest() throws IOException {
23HttpGet httpGet = new HttpGet("https://apihk.mch.weixin.qq.com/v3/global/certificates");
24httpGet.addHeader("Accept", "application/json");
25httpGet.addHeader("Content-type", "application/json; charset=utf-8");
26System.out.println(getToken(httpGet,""));
27}
28
29public String getToken(HttpRequestBase request, String body) throws IOException {
30String nonceStr = generateNonceStr();
31long timestamp = generateTimestamp();
32
33String message = buildMessage(nonceStr, timestamp, request, body);
34// log.debug("authorization message=[{}]", message);
35String signature = sign(message.getBytes(StandardCharsets.UTF_8));
36
37String token = "mchid=\"" + merchantId + "\","
38+ "nonce_str=\"" + nonceStr + "\","
39+ "timestamp=\"" + timestamp + "\","
40+ "serial_no=\"" + certificateSerialNumber + "\","
41+ "signature=\"" + signature + "\"";
42// log.debug("authorization token=[{}]", token);
43
44return token;
45}
46
47public String sign(byte[] message) {
48try {
49Signature sign = Signature.getInstance("SHA256withRSA");
50sign.initSign(privateKey);
51sign.update(message);
52return Base64.getEncoder().encodeToString(sign.sign());
53} catch (NoSuchAlgorithmException e) {
54throw new RuntimeException("当前Java环境不支持SHA256withRSA", e);
55} catch (SignatureException e) {
56throw new RuntimeException("签名计算失败", e);
57} catch (InvalidKeyException e) {
58throw new RuntimeException("无效的私钥", e);
59}
60}
61
62protected String buildMessage(String nonce, long timestamp, HttpRequestBase request, String body) throws IOException {
63URI uri = request.getURI();
64String canonicalUrl = uri.getRawPath();
65if (uri.getQuery() != null) {
66canonicalUrl += "?" + uri.getRawQuery();
67}
68
69return request.getRequestLine().getMethod() + "\n"
70+ canonicalUrl + "\n"
71+ timestamp + "\n"
72+ nonce + "\n"
73+ body + "\n";
74}
75
76protected long generateTimestamp() {
77return System.currentTimeMillis() / 1000;
78}
79
80protected String generateNonceStr() {
81char[] nonceChars = new char[32];
82for (int index = 0; index < nonceChars.length; ++index) {
83nonceChars[index] = SYMBOLS.charAt(RANDOM.nextInt(SYMBOLS.length()));
84}
85return new String(nonceChars);
86}
87}

PHP

1<? php
2require_once('vendor/autoload.php');
3
4use WeChatPay\ Crypto\ Rsa;
5
6class SignTest {
7	public static
8	function timestamp(): int {
9		return time();
10	}
11
12	public static
13	function nonce(int $size = 32): string {
14		if ($size < 1) {
15			throw new InvalidArgumentException('Size must be a positive integer.');
16		}
17
18		return implode('', array_map(static
19			function(string $c): string {
20				return '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz' [ord($c) % 62];
21			}, str_split(random_bytes($size))));
22	}
23
24	public static
25	function request(string $method, string $uri, string $timestamp, string $nonce, string $body = ''): string {
26		return static::joinedByLineFeed($method, $uri, $timestamp, $nonce, $body);
27	}
28
29	public static
30	function joinedByLineFeed(...$pieces): string {
31		return implode("\n", array_merge($pieces, ['']));
32	}
33
34	public static
35	function sign(string $message, $privateKey): string {
36		if (!openssl_sign($message, $signature, $privateKey, OPENSSL_ALGO_SHA256)) {
37			throw new UnexpectedValueException('Signing the input $message failed, please checking your $privateKey whether or nor correct.');
38		}
39
40		return base64_encode($signature);
41	}
42
43	public static
44	function authorization(string $mchid, $privateKey, string $serial, string $method, string $uri, string $body = ''): string {
45		$nonce = static::nonce();
46		$timestamp = static::timestamp();
47		$signature = static::sign(static::request($method, $uri, $timestamp, $nonce, $body), $privateKey);
48		return sprintf(
49			'WECHATPAY2-SHA256-RSA2048 mchid="%s",serial_no="%s",timestamp="%s",nonce_str="%s",signature="%s"',
50			$mchid, $serial, $timestamp, $nonce, $signature
51		);
52	}
53}
54
55$merchantPrivateKeyFilePath = '/path/to/your/private/key/file';
56$merchantId = '10010000';
57$method = 'GET';
58$uri = 'v3/global/certificates';
59$merchantCertificateSerial = '329E9A85CDAAFAD289AA69AE71369CBF8A1290A2';
60$merchantPrivateKeyFileContent = file_get_contents($merchantPrivateKeyFilePath);
61$merchantPrivateKey = Rsa::from($merchantPrivateKeyFileContent, Rsa::KEY_TYPE_PRIVATE);
62
63echo SignTest::authorization('', $merchantPrivateKey, $merchantCertificateSerial, $method, $uri);

GO

1package sign
2
3import (
4	"crypto"
5	"crypto/rand"
6	"crypto/rsa"
7	"crypto/x509"
8	"encoding/base64"
9	"encoding/pem"
10	"fmt"
11	"github.com/wechatpay-apiv3/wechatpay-go/utils"
12	"log"
13	"testing"
14	"time"
15)
16
17const (
18	NonceSymbols              = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
19	NonceLength               = 32
20	SignatureMessageFormat    = "%s\n%s\n%d\n%s\n%s\n"
21	HeaderAuthorizationFormat = "%s mchid=\"%s\",nonce_str=\"%s\",timestamp=\"%d\",serial_no=\"%s\",signature=\"%s\""
22)
23
24var (
25	signTestMchid            = ""                               // merchant id
26	signTestCertSerialNumber = "" // merchant certificate serial number
27	signTestPrivateKey       *rsa.PrivateKey
28)
29
30func GenerateNonce() (string, error) {
31	bytes := make([]byte, NonceLength)
32	_, err := rand.Read(bytes)
33	if err != nil {
34		return "", err
35	}
36	symbolsByteLength := byte(len(NonceSymbols))
37	for i, b := range bytes {
38		bytes[i] = NonceSymbols[b%symbolsByteLength]
39	}
40	return string(bytes), nil
41}
42
43// LoadPrivateKey 通过私钥的文本内容加载私钥
44func LoadPrivateKey(privateKeyStr string) (privateKey *rsa.PrivateKey, err error) {
45	block, _ := pem.Decode([]byte(privateKeyStr))
46	if block == nil {
47		return nil, fmt.Errorf("decode private key err")
48	}
49	if block.Type != "PRIVATE KEY" {
50		return nil, fmt.Errorf("the kind of PEM should be PRVATE KEY")
51	}
52	key, err := x509.ParsePKCS8PrivateKey(block.Bytes)
53	if err != nil {
54		return nil, fmt.Errorf("parse private key err:%s", err.Error())
55	}
56	privateKey, ok := key.(*rsa.PrivateKey)
57	if !ok {
58		return nil, fmt.Errorf("%s is not rsa private key", privateKeyStr)
59	}
60	return privateKey, nil
61}
62
63func SignSHA256WithRSA(source string, privateKey *rsa.PrivateKey) (signature string, err error) {
64	if privateKey == nil {
65		return "", fmt.Errorf("private key should not be nil")
66	}
67	h := crypto.Hash.New(crypto.SHA256)
68	_, err = h.Write([]byte(source))
69	if err != nil {
70		return "", nil
71	}
72	hashed := h.Sum(nil)
73	signatureByte, err := rsa.SignPKCS1v15(rand.Reader, privateKey, crypto.SHA256, hashed)
74	if err != nil {
75		return "", err
76	}
77	return base64.StdEncoding.EncodeToString(signatureByte), nil
78}
79
80func getAuthorizationType() string {
81	return "WECHATPAY2-SHA256-RSA2048"
82}
83
84func GenerateAuthorizationHeader(mchid, certificateSerialNo, method, canonicalURL, body string, privateKey *rsa.PrivateKey) (string, error) {
85	nonce, err := GenerateNonce()
86	if err != nil {
87		return "", err
88	}
89	timestamp := time.Now().Unix()
90	message := fmt.Sprintf(SignatureMessageFormat, method, canonicalURL, timestamp, nonce, body)
91	signatureResult, err := SignSHA256WithRSA(message, privateKey)
92	if err != nil {
93		return "", err
94	}
95	authorization := fmt.Sprintf(
96		HeaderAuthorizationFormat, getAuthorizationType(),
97		mchid, nonce, timestamp, certificateSerialNo, signatureResult,
98	)
99	return authorization, nil
100}
101
102func TestGenerateAuthorizationHeader(t *testing.T) {
103	// 使用 utils 提供的函数从本地文件中加载商户私钥,商户私钥会用来生成请求的签名
104	signTestPrivateKey, err = utils.LoadPrivateKeyWithPath("/Path/to/your/private_key.pem")
105	if err != nil {
106		log.Fatal("load merchant private key error")
107	}
108	authHeader, err := GenerateAuthorizationHeader(signTestMchid, signTestCertSerialNumber, "GET", "/v3/global/certificates", "", signTestPrivateKey)
109	if err != nil {
110		log.Fatal(err)
111	}
112	log.Printf("The authorization header is: %s", authHeader)
113}

Notice

If your request returns a signature error 401 Unauthorized, please refer to FAQs on Signatures

 

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.