添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

构造签名串

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

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

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

第一步,获取HTTP请求的方法( GET , POST , PUT )等

第二步,获取请求的绝对URL,并去除域名部分得到参与签名的URL。如果请求中有查询参数,URL末尾应附加有'?'和对应的查询字符串。
/v3/certificates
第三步,获取发起请求时的系统当前时间戳,即格林威治时间1970年01月01日00时00分00秒(北京时间1970年01月01日08时00分00秒)起至现在的总秒数,作为请求时间戳。微信支付会拒绝处理很久之前发起的请求,请商户保持自身系统的时间准确。
$ date +%s
1554208460
第四步,生成一个请求随机串,可参见 生成随机数算法 。这里,我们使用命令行直接生成一个。
$ 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
    $ 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== 微信支付商户API v3要求请求通过 HTTP Authorization 头来传递签名。 Authorization 认证类型 签名信息 两个部分组成。

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

    Authorization: 认证类型 签名信息
    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请求了。

    $ 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"'
    // 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";
                      
    // 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);
        // 使用方法
        // 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();
                //适用该方法的版本https://learn.microsoft.com/zh-cn/dotnet/api/system.security.cryptography.asymmetricalgorithm.importpkcs8privatekey?view=net-7.0
    rsa.ImportPkcs8PrivateKey(keyData, out _);
                rsa.ImportPkcs8PrivateKey(keyData, out _);
                byte[] data = System.Text.Encoding.UTF8.GetBytes(message);
                return Convert.ToBase64String(rsa.SignData(data, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1));