如何生成请求签名
更新时间:2024.12.24我们提供了微信支付API v3官方SDK(目前包含Java (opens new window)、PHP (opens new window)、 Go (opens new window)三种语言版本),使用官方 SDK 调用微信支付接口,无需关心签名生成和验证,接入更方便。
商户可以按照下述步骤生成请求的签名。在本节的最后,我们准备了多种常用编程语言的演示代码供开发者参考。
微信支付API v3 要求商户对请求进行签名,微信支付会在收到请求后进行签名的验证。如果签名验证不通过,微信支付API v3将会拒绝处理请求,并返回401 Unauthorized。
# 1. 准备
商户需要拥有一个微信支付商户号,并通过超级管理员账号登录商户平台,获取商户API证书。 商户API证书的压缩包中包含了签名必需的私钥和商户证书。
# 2. 构造签名串
我们希望商户的技术开发人员按照当前文档约定的规则构造签名串。微信支付会使用同样的方式构造签名串。如果商户构造签名串的方式错误,将导致签名验证不通过。下面先说明签名串的具体格式。
签名串一共有五行,每一行为一个参数。结尾以\n
(换行符,ASCII编码值为0x0A)结束,包括最后一行。如果参数本身以\n
结束,也需要附加一个\n
。
1HTTP请求方法\n2URL\n3请求时间戳\n4请求随机串\n5请求报文主体\n
我们通过在命令行中调用"获取微信支付平台证书"接口,一步一步向开发者介绍如何进行请求签名。按照接口文档,获取商户平台证书的URL为https://api.mch.weixin.qq.com/v3/certificates
,请求方法为GET,没有查询参数。
第一步,获取HTTP请求的方法(GET,POST,PUT)等
1GET
第二步,获取请求的绝对URL,并去除域名部分得到参与签名的URL。如果请求中有查询参数,URL末尾应附加有'?'和对应的查询字符串。
1/v3/certificates
第三步,获取发起请求时的系统当前时间戳,即格林威治时间1970年01月01日00时00分00秒(北京时间1970年01月01日08时00分00秒)起至现在的总秒数,作为请求时间戳。微信支付会拒绝处理很久之前发起的请求,请商户保持自身系统的时间准确。
1$ date +%s 21554208460
第四步,生成一个请求随机串,我们推荐生成随机数算法如下:调用随机数函数生成,将得到的值转换为字符串。这里,我们使用命令行直接生成一个。
1$ hexdump -n 16 -e '4/4 "%08X" 1 "\n"' /dev/random 2593BEC0C930BF1AFEB40B4A08C8FB242
第五步,获取请求中的请求报文主体(request body)。
- 请求方法为GET时,报文主体为空。
- 当请求方法为POST或PUT时,请使用真实发送的JSON报文。
- 图片上传API,请使用meta对应的JSON报文。
对于下载证书的接口来说,请求报文主体是一个空串。
第六步,按照前述规则,构造的请求签名串为:
1GET\n 2/v3/certificates\n 31554208460\n 4593BEC0C930BF1AFEB40B4A08C8FB242\n 5\n
# 3. 计算签名值
绝大多数编程语言提供的签名函数支持对签名数据进行签名。强烈建议商户调用该类函数,使用商户私钥对待签名串进行SHA256 with RSA签名,并对签名结果进行Base64编码得到签名值。
1$ echo -n -e \2 "GET\n/v3/certificates\n1554208460\n593BEC0C930BF1AFEB40B4A08C8FB242\n\n" \3 | openssl dgst -sha256 -sign apiclient_key.pem \4 | openssl base64 -A5 uOVRnA4qG/MNnYzdQxJanN+zU+lTgIcnU9BxGw5dKjK+VdEUz2FeIoC+D5sB/LN+nGzX3hfZg6r5wT1pl2ZobmIc6p0ldN7J6yDgUzbX8Uk3sD4a4eZVPTBvqNDoUqcYMlZ9uuDdCvNv4TM3c1WzsXUrExwVkI1XO5jCNbgDJ25nkT/c1gIFvqoogl7MdSFGc4W4xZsqCItnqbypR3RuGIlR9h9vlRsy7zJR9PBI83X8alLDIfR1ukt1P7tMnmogZ0cuDY8cZsd8ZlCgLadmvej58SLsIkVxFJ8XyUgx9FmutKSYTmYtWBZ0+tNvfGmbXU7cob8H/4nLBiCwIUFluw==
# 4. 设置HTTP头
微信支付商户API v3要求请求通过HTTP Authorization头来传递签名。Authorization由认证类型和签名信息两个部分组成。
下面我们使用命令行演示如何生成签名。
1Authorization: 认证类型 签名信息
具体组成为:
- 认证类型,目前为WECHATPAY2-SHA256-RSA2048
- 签名信息
- 发起请求的商户(包括直连商户、服务商或渠道商)的商户号mchid
- 商户API证书序列号serial_no,用于声明所使用的证书
- 请求随机串nonce_str
- 时间戳timestamp
- 签名值signature
提示
注意:以上五项签名信息,无顺序要求。
Authorization头的示例如下:(注意,示例因为排版可能存在换行,实际数据应在一行)
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"
最终我们可以组一个包含了签名的HTTP请求了。
1$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"'
# 5 演示代码
开发者可以查看SDK相关章节,获取对应语言的库。如何在程序中加载私钥,请参考证书和密钥常见问题。
计算签名的示例代码如下。
1import okhttp3.HttpUrl;2import java.security.Signature;3import java.util.Base64;45// Authorization: <schema> <token>6// GET - getToken("GET", httpurl, "")7// POST - getToken("POST", httpurl, json)8String schema = "WECHATPAY2-SHA256-RSA2048";9HttpUrl httpurl = HttpUrl.parse(url);1011String getToken(String method, HttpUrl url, String body) {12 String nonceStr = "your nonce string";13 long timestamp = System.currentTimeMillis() / 1000;14 String message = buildMessage(method, url, timestamp, nonceStr, body);15 String signature = sign(message.getBytes("utf-8"));1617 return "mchid=\"" + yourMerchantId + "\","18 + "nonce_str=\"" + nonceStr + "\","19 + "timestamp=\"" + timestamp + "\","20 + "serial_no=\"" + yourCertificateSerialNo + "\","21 + "signature=\"" + signature + "\"";22}2324String sign(byte[] message) {25 Signature sign = Signature.getInstance("SHA256withRSA");26 sign.initSign(yourPrivateKey);27 sign.update(message);2829 return Base64.getEncoder().encodeToString(sign.sign());30}3132String buildMessage(String method, HttpUrl url, long timestamp, String nonceStr, String body) {33 String canonicalUrl = url.encodedPath();34 if (url.encodedQuery() != null) {35 canonicalUrl += "?" + url.encodedQuery();36 }3738 return method + "\n"39 + canonicalUrl + "\n"40 + timestamp + "\n"41 + nonceStr + "\n"42 + body + "\n";
1// Authorization: <schema> <token>2$url_parts = parse_url($url);3$canonical_url = ($url_parts['path'] . (!empty($url_parts['query']) ? "?${url_parts['query']}" : ""));4$message = $http_method."\n".5$canonical_url."\n".6$timestamp."\n".7$nonce."\n".8$body."\n";9 10openssl_sign($message, $raw_sign, $mch_private_key, 'sha256WithRSAEncryption');11$sign = base64_encode($raw_sign);12 13$schema = 'WECHATPAY2-SHA256-RSA2048';14$token = sprintf('mchid="%s",nonce_str="%s",timestamp="%d",serial_no="%s",signature="%s"',15$merchant_id, $nonce, $timestamp, $serial_no, $sign);
1using System;2using System.IO;3using System.Net.Http;4using System.Security.Cryptography;5using System.Threading;6using System.Threading.Tasks;78namespace HttpHandlerDemo9{10 // 使用方法11 // HttpClient client = new HttpClient(new HttpHandler("{商户号}", "{商户证书序列号}"));12 // ...13 // var response = client.GetAsync("https://api.mch.weixin.qq.com/v3/certificates");14 public class HttpHandler : DelegatingHandler15 {16 private readonly string merchantId;17 private readonly string serialNo;1819 public HttpHandler(string merchantId, string merchantSerialNo)20 {21 InnerHandler = new HttpClientHandler();2223 this.merchantId = merchantId;24 this.serialNo = merchantSerialNo;25 }2627 protected async override Task<HttpResponseMessage> SendAsync(28 HttpRequestMessage request,29 CancellationToken cancellationToken)30 {31 var auth = await BuildAuthAsync(request);32 string value = $"WECHATPAY2-SHA256-RSA2048 {auth}";33 request.Headers.Add("Authorization", value);3435 return await base.SendAsync(request, cancellationToken);36 }3738 protected async Task<string> BuildAuthAsync(HttpRequestMessage request)39 {40 string method = request.Method.ToString();41 string body = "";42 if (method == "POST" || method == "PUT" || method == "PATCH")43 {44 var content = request.Content;45 body = await content.ReadAsStringAsync();46 }4748 string uri = request.RequestUri.PathAndQuery;49 var timestamp = DateTimeOffset.Now.ToUnixTimeSeconds();50 string nonce = Path.GetRandomFileName();5152 string message = $"{method}\n{uri}\n{timestamp}\n{nonce}\n{body}\n";53 string signature = Sign(message);54 return $"mchid=\"{merchantId}\",nonce_str=\"{nonce}\",timestamp=\"{timestamp}\",serial_no=\"{serialNo}\",signature=\"{signature}\"";55 }5657 protected string Sign(string message)58 {59 // NOTE: 私钥不包括私钥文件起始的-----BEGIN PRIVATE KEY-----60 // 亦不包括结尾的-----END PRIVATE KEY-----61 string privateKey = "{你的私钥}";62 byte[] keyData = Convert.FromBase64String(privateKey);63 64 var rsa = RSA.Create();65 //适用该方法的版本https://learn.microsoft.com/zh-cn/dotnet/api/system.security.cryptography.asymmetricalgorithm.importpkcs8privatekey?view=net-7.066 rsa.ImportPkcs8PrivateKey(keyData, out _);67 byte[] data = System.Text.Encoding.UTF8.GetBytes(message);68 return Convert.ToBase64String(rsa.SignData(data, HashAlgorithmName.SHA256, RSASignaturePadding.Pkcs1));69 }70 }71}
如果您的请求返回了签名错误401 Unauthorized,请参考签名常见问题