> For the complete documentation index, see [llms.txt](https://wechatpay-api.gitbook.io/wechatpay-api-v3/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://wechatpay-api.gitbook.io/wechatpay-api-v3/qian-ming-zhi-nan-1/qian-ming-sheng-cheng.md).

# 签名生成

微信支付API v3要求商户对请求进行签名。微信支付会在收到请求后进行签名的验证。如果签名验证不通过，微信支付API v3将会拒绝处理请求，并返回`401 Unauthorized`。

## 准备

商户需要拥有一个微信支付商户号，并通过超级管理员账号登陆商户平台，获取[商户API证书](/wechatpay-api-v3/ren-zheng/zheng-shu.md#shang-hu-api-zheng-shu)。商户API证书的压缩包中包含了签名必需的私钥和商户证书。

## 构造签名串

我们希望商户的技术开发人员按照当前文档约定的规则构造签名串。微信支付会使用同样的方式构造签名串。如果商户构造签名串的方式错误，将导致签名验证不通过。下面先说明签名串的具体格式。

签名串一共有五行，每一行为一个参数。行尾以`\n`（换行符，ASCII编码值为0x0A）结束，包括最后一行。如果参数本身以`\n`结束，也需要附加一个`\n`。

```
HTTP请求方法\n
URL\n
请求时间戳\n
请求随机串\n
请求报文主体\n
```

我们通过在命令行中调用"获取微信支付平台证书"接口，一步一步向开发者介绍如何进行请求签名。按照接口文档，获取商户平台证书的URL为`https://api.mch.weixin.qq.com/v3/certificates`，请求方法为`GET`，没有查询参数。

第一步，获取HTTP请求的方法（`GET`,`POST`,`PUT`等）

```http
GET
```

第二步，获取请求的绝对URL，并去除域名部分得到参与签名的URL。如果请求中有查询参数，URL末尾应附加有'?'和对应的查询字符串。

```http
/v3/certificates
```

第三步，获取发起请求时的系统当前时间戳，即格林威治时间1970年01月01日00时00分00秒(北京时间1970年01月01日08时00分00秒)起至现在的总秒数，作为请求时间戳。微信支付会拒绝处理很久之前发起的请求，请商户保持自身系统的时间准确。

```bash
$ date +%s
1554208460
```

第四步，生成一个请求随机串，可参见[生成随机数算法](https://pay.weixin.qq.com/wiki/doc/api/micropay.php?chapter=4_3)。这里，我们使用命令行直接生成一个。

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

第五步，获取请求中的请求报文主体（request body）。

* 请求方法为`GET`时，报文主体为空。
* 当请求方法为`POST`或`PUT`时，请使用**真实发送**的`JSON`报文。
* 图片上传API，请使用`meta`对应的`JSON`报文。

对于下载证书的接口来说，请求报文主体是一个空串。

第六步，按照前述规则，构造的请求签名串为：

```
GET\n
/v3/certificates\n
1554208460\n
593BEC0C930BF1AFEB40B4A08C8FB242\n
\n
```

## 计算签名值

绝大多数编程语言提供的签名函数支持对**签名数据**进行签名。强烈建议商户调用该类函数，使用商户私钥对**待签名串**进行SHA256 with RSA签名，并对签名结果进行**Base64编码**得到签名值。

下面我们使用命令行演示如何生成签名。

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

## 设置HTTP头

微信支付商户API v3要求请求通过HTTP`Authorization`头来传递签名。`Authorization`由**认证类型**和**签名信息**两个部分组成。

```
Authorization: 认证类型 签名信息
```

具体组成为：

1. 认证类型，目前为`WECHATPAY2-SHA256-RSA2048`
2. 签名信息

   * 发起请求的商户（包括直连商户、服务商或渠道商）的商户号`mchid`
   * [商户API证书](/wechatpay-api-v3/ren-zheng/zheng-shu.md#shang-hu-api-zheng-shu)序列号`serial_no`，用于[声明所使用的证书](/wechatpay-api-v3/ren-zheng/zheng-shu.md#sheng-ming-suo-shi-yong-de-zheng-shu)
   * 请求随机串`nonce_str`
   * 时间戳`timestamp`
   * 签名值`signature`

   注：以上五项签名信息，无顺序要求。

`Authorization`头的示例如下：（注意，示例因为排版可能存在换行，实际数据应在一行）

```http
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"
```

最终我们可以组一个包含了签名的HTTP请求了。

```bash
$ curl https://api.mch.weixin.qq.com/v3/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"'
```

## 演示代码

开发者可以查看[开发工具](/wechatpay-api-v3/kai-fa-gong-ju.md)相关章节，获取对应语言的库。如何在程序中加载私钥，请参考[常见问题](/wechatpay-api-v3/chang-jian-wen-ti/qian-ming-xiang-guan.md#ru-he-zai-cheng-xu-zhong-jia-zai-si-yao)。

计算签名的示例代码如下。

{% tabs %}
{% tab title="Java" %}

```java
import okhttp3.HttpUrl;
import java.security.Signature;
import java.util.Base64;

// Authorization: <schema> <token>
// GET - getToken("GET", httpurl, "")
// POST - getToken("POST", httpurl, json)
String schema = "WECHATPAY2-SHA256-RSA2048";
HttpUrl httpurl = HttpUrl.parse(url);

String getToken(String method, HttpUrl url, String body) {
    String nonceStr = "your nonce string";
    long timestamp = System.currentTimeMillis() / 1000;
    String message = buildMessage(method, url, timestamp, nonceStr, body);
    String signature = sign(message.getBytes("utf-8"));

    return "mchid=\"" + yourMerchantId + "\","
    + "nonce_str=\"" + nonceStr + "\","
    + "timestamp=\"" + timestamp + "\","
    + "serial_no=\"" + yourCertificateSerialNo + "\","
    + "signature=\"" + signature + "\"";
}

String sign(byte[] message) {
    Signature sign = Signature.getInstance("SHA256withRSA");
    sign.initSign(yourPrivateKey);
    sign.update(message);

    return Base64.getEncoder().encodeToString(sign.sign());
}

String buildMessage(String method, HttpUrl url, long timestamp, String nonceStr, String body) {
    String canonicalUrl = url.encodedPath();
    if (url.encodedQuery() != null) {
      canonicalUrl += "?" + url.encodedQuery();
    }

    return method + "\n"
        + canonicalUrl + "\n"
        + timestamp + "\n"
        + nonceStr + "\n"
        + body + "\n";
}
```

{% endtab %}

{% tab title="PHP" %}

```php
// Authorization: <schema> <token>
$url_parts = parse_url($url);
$canonical_url = ($url_parts['path'] . (!empty($url_parts['query']) ? "?${url_parts['query']}" : ""));
$message = $http_method."\n".
    $canonical_url."\n".
    $timestamp."\n".
    $nonce."\n".
    $body."\n";

openssl_sign($message, $raw_sign, $mch_private_key, 'sha256WithRSAEncryption');
$sign = base64_encode($raw_sign);

$schema = 'WECHATPAY2-SHA256-RSA2048';
$token = sprintf('mchid="%s",nonce_str="%s",timestamp="%d",serial_no="%s",signature="%s"',
    $merchant_id, $nonce, $timestamp, $serial_no, $sign);
```

{% endtab %}

{% tab title=".Net" %}
示例实现了一个HttpClient的`DelegatingHandler`。该Message Handler将根据请求生成并设置签名。

```csharp
using System;
using System.IO;
using System.Net.Http;
using System.Security.Cryptography;
using System.Threading;
using System.Threading.Tasks;

namespace HttpHandlerDemo
{
    // 使用方法
    // HttpClient client = new HttpClient(new HttpHandler("{商户号}", "{商户证书序列号}"));
    // ...
    // var response = client.GetAsync("https://api.mch.weixin.qq.com/v3/certificates");
    public class HttpHandler : DelegatingHandler
    {
        private readonly string merchantId;
        private readonly string serialNo;

        public HttpHandler(string merchantId, string merchantSerialNo)
        {
            InnerHandler = new HttpClientHandler();

            this.merchantId = merchantId;
            this.serialNo = merchantSerialNo;
        }

        protected async override Task<HttpResponseMessage> SendAsync(
            HttpRequestMessage request,
            CancellationToken cancellationToken)
        {
            var auth = await BuildAuthAsync(request);
            string value = $"WECHATPAY2-SHA256-RSA2048 {auth}";
            request.Headers.Add("Authorization", value);

            return await base.SendAsync(request, cancellationToken);
        }

        protected async Task<string> BuildAuthAsync(HttpRequestMessage request)
        {
            string method = request.Method.ToString();
            string body = "";
            if (method == "POST" || method == "PUT" || method == "PATCH")
            {
                var content = request.Content;
                body = await content.ReadAsStringAsync();
            }

            string uri = request.RequestUri.PathAndQuery;
            var timestamp = DateTimeOffset.Now.ToUnixTimeSeconds();
            string nonce = Path.GetRandomFileName();

            string message = $"{method}\n{uri}\n{timestamp}\n{nonce}\n{body}\n";
            string signature = Sign(message);
            return $"mchid=\"{merchantId}\",nonce_str=\"{nonce}\",timestamp=\"{timestamp}\",serial_no=\"{serialNo}\",signature=\"{signature}\"";
        }

        protected string Sign(string message)
        {
            // NOTE： 私钥不包括私钥文件起始的-----BEGIN PRIVATE KEY-----
            //        亦不包括结尾的-----END PRIVATE KEY-----
            string privateKey = "{你的私钥}";
            byte[] keyData = Convert.FromBase64String(privateKey);
            
            var rsa = RSA.Create();
            rsa.ImportPkcs8PrivateKey(keyData, out _);
            byte[] data = System.Text.Encoding.UTF8.GetBytes(message);
            return Convert.ToBase64String(rsa.SignData(data, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1));
        }
    }
}
```

{% endtab %}
{% endtabs %}

{% hint style="info" %}
如果您的请求返回了签名错误`401 Unauthorized`，请参考[常见问题之签名相关](/wechatpay-api-v3/chang-jian-wen-ti/qian-ming-xiang-guan.md)
{% endhint %}
