簽名生成
商戶可以按照下述步驟生成請(qǐng)求的簽名。在本節(jié)的最后,我們準(zhǔn)備了多種常用編程語(yǔ)言的演示代碼供開(kāi)發(fā)者參考。
商戶可以按照下述步驟生成請(qǐng)求的簽名。在本節(jié)的最后,我們準(zhǔn)備了多種常用編程語(yǔ)言的演示代碼供開(kāi)發(fā)者參考。
微信支付API V3 要求商戶對(duì)請(qǐng)求進(jìn)行簽名。微信支付會(huì)在收到請(qǐng)求后進(jìn)行簽名的驗(yàn)證。如果簽名驗(yàn)證不通過(guò),微信支付API V3將會(huì)拒絕處理請(qǐng)求,并返回401 Unauthorized。
商戶需要擁有一個(gè)微信支付商戶號(hào),并通過(guò)超級(jí)管理員賬號(hào)登錄商戶平臺(tái),獲取商戶API證書(shū)。 商戶API證書(shū)的壓縮包中包含了簽名必需的私鑰和商戶證書(shū)。
我們希望商戶的技術(shù)開(kāi)發(fā)人員按照當(dāng)前文檔約定的規(guī)則構(gòu)造簽名串。微信支付會(huì)使用同樣的方式構(gòu)造簽名串。如果商戶構(gòu)造簽名串的方式錯(cuò)誤,將導(dǎo)致簽名驗(yàn)證不通過(guò)。下面先說(shuō)明簽名串的具體格式。
簽名串一共有五行,每一行為一個(gè)參數(shù)。行尾以 \n(換行符,ASCII編碼值為0x0A)結(jié)束,包括最后一行。如果參數(shù)本身以\n結(jié)束,也需要附加一個(gè)\n。
HTTP請(qǐng)求方法\ n
URL\ n
請(qǐng)求時(shí)間戳\ n
請(qǐng)求隨機(jī)串\ n
請(qǐng)求報(bào)文主體\ n
我們通過(guò)在命令行中調(diào)用"獲取微信支付平臺(tái)證書(shū)"接口,一步一步向開(kāi)發(fā)者介紹如何進(jìn)行請(qǐng)求簽名。按照接口文檔,獲取商戶平臺(tái)證書(shū)的URL為 https://apihk.mch.weixin.qq.com/v3/global/certificates請(qǐng)求方法為GET,沒(méi)有查詢參數(shù)。
第一步,獲取HTTP請(qǐng)求的方法(GET,POST,PUT)等
GET
第二步,獲取請(qǐng)求的絕對(duì)URL,并去除域名部分得到參與簽名的URL。如果請(qǐng)求中有查詢參數(shù),URL末尾應(yīng)附加有'?'和對(duì)應(yīng)的查詢字符串。
/v3/global/certificates
第三步,獲取發(fā)起請(qǐng)求時(shí)的系統(tǒng)當(dāng)前時(shí)間戳,即格林威治時(shí)間1970年01月01日00時(shí)00分00秒(北京時(shí)間1970年01月01日08時(shí)00分00秒)起至現(xiàn)在的總秒數(shù),作為請(qǐng)求時(shí)間戳。微信支付會(huì)拒絕處理很久之前發(fā)起的請(qǐng)求,請(qǐng)商戶保持自身系統(tǒng)的時(shí)間準(zhǔn)確。
$ date +%s
1554208460
第四步,生成一個(gè)請(qǐng)求隨機(jī)串,可參見(jiàn)生成隨機(jī)數(shù)算法。這里,我們使用命令行直接生成一個(gè)。
$ hexdump -n 16 -e '4/4 "%08X" 1 "\n"' /dev/random
593BEC0C930BF1AFEB40B4A08C8FB242
第五步,獲取請(qǐng)求中的請(qǐng)求報(bào)文主體(request body)。
? 請(qǐng)求方法為GET時(shí),報(bào)文主體為空。
? 當(dāng)請(qǐng)求方法為POST或PUT時(shí),請(qǐng)使用真實(shí)發(fā)送的JSON報(bào)文。
? 圖片上傳API,請(qǐng)使用meta對(duì)應(yīng)的JSON報(bào)文。
對(duì)于下載證書(shū)的接口來(lái)說(shuō),請(qǐng)求報(bào)文主體是一個(gè)空串。
第六步,按照前述規(guī)則,構(gòu)造的請(qǐng)求簽名串為:
GET\n
/v3/global/certificates\n
1554208460\n
593BEC0C930BF1AFEB40B4A08C8FB242\n
\n
絕大多數(shù)編程語(yǔ)言提供的簽名函數(shù)支持對(duì)簽名數(shù)據(jù)進(jìn)行簽名。強(qiáng)烈建議商戶調(diào)用該類(lèi)函數(shù),使用商戶私鑰對(duì)待簽名串進(jìn)行SHA256 with RSA簽名,并對(duì)簽名結(jié)果進(jìn)行Base64編碼得到簽名值。
$ echo -n -e \
"GET\n/v3/global/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要求請(qǐng)求通過(guò)HTTP Authorization頭來(lái)傳遞簽名。 Authorization由認(rèn)證類(lèi)型和簽名信息兩個(gè)部分組成。
下面我們使用命令行演示如何生成簽名。
Authorization: 認(rèn)證類(lèi)型 簽名信息
具體組成為:
1.認(rèn)證類(lèi)型,目前為WECHATPAY2-SHA256-RSA2048
2.簽名信息
? 發(fā)起請(qǐng)求的商戶(包括直連商戶、服務(wù)商或渠道商)的商戶號(hào) mchid
? 商戶API證書(shū)序列號(hào)serial_no
,用于聲明所使用的證書(shū)
? 請(qǐng)求隨機(jī)串nonce_str
? 時(shí)間戳timestamp
? 簽名值signature
Authorization
頭的示例如下:(注意,示例因?yàn)榕虐婵赡艽嬖趽Q行,實(shí)際數(shù)據(jù)應(yīng)在一行)
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"
最終我們可以組一個(gè)包含了簽名的HTTP請(qǐng)求了。
$ 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"'
開(kāi)發(fā)者可以查看開(kāi)發(fā)工具 相關(guān)章節(jié),獲取對(duì)應(yīng)語(yǔ)言的庫(kù)。如何在程序中加載私鑰,請(qǐng)參考常見(jiàn)問(wèn)題 。
計(jì)算簽名的示例代碼如下。
import okhttp3.HttpUrl;
package com.wechat.v3;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpRequestBase;
import org.junit.Test;
import java.io.IOException;
import java.net.URI;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.util.Base64;
public class SignTest {
protected static final String SYMBOLS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
protected static final SecureRandom RANDOM = new SecureRandom();
protected static final PrivateKey privateKey = null; //need to be initialized
protected static final String certificateSerialNumber = null; //need to be initialized
protected static final String merchantId = null; //need to be initialized
@Test
public void signTest() throws IOException {
HttpGet httpGet = new HttpGet("https://apihk.mch.weixin.qq.com/v3/global/certificates");
httpGet.addHeader("Accept", "application/json");
httpGet.addHeader("Content-type", "application/json; charset=utf-8");
System.out.println(getToken(httpGet,""));
}
public String getToken(HttpRequestBase request, String body) throws IOException {
String nonceStr = generateNonceStr();
long timestamp = generateTimestamp();
String message = buildMessage(nonceStr, timestamp, request, body);
// log.debug("authorization message=[{}]", message);
String signature = sign(message.getBytes(StandardCharsets.UTF_8));
String token = "mchid=\"" + merchantId + "\","
+ "nonce_str=\"" + nonceStr + "\","
+ "timestamp=\"" + timestamp + "\","
+ "serial_no=\"" + certificateSerialNumber + "\","
+ "signature=\"" + signature + "\"";
// log.debug("authorization token=[{}]", token);
return token;
}
public String sign(byte[] message) {
try {
Signature sign = Signature.getInstance("SHA256withRSA");
sign.initSign(privateKey);
sign.update(message);
return Base64.getEncoder().encodeToString(sign.sign());
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException("當(dāng)前Java環(huán)境不支持SHA256withRSA", e);
} catch (SignatureException e) {
throw new RuntimeException("簽名計(jì)算失敗", e);
} catch (InvalidKeyException e) {
throw new RuntimeException("無(wú)效的私鑰", e);
}
}
protected String buildMessage(String nonce, long timestamp, HttpRequestBase request, String body) throws IOException {
URI uri = request.getURI();
String canonicalUrl = uri.getRawPath();
if (uri.getQuery() != null) {
canonicalUrl += "?" + uri.getRawQuery();
}
return request.getRequestLine().getMethod() + "\n"
+ canonicalUrl + "\n"
+ timestamp + "\n"
+ nonce + "\n"
+ body + "\n";
}
protected long generateTimestamp() {
return System.currentTimeMillis() / 1000;
}
protected String generateNonceStr() {
char[] nonceChars = new char[32];
for (int index = 0; index < nonceChars.length; ++index) {
nonceChars[index] = SYMBOLS.charAt(RANDOM.nextInt(SYMBOLS.length()));
}
return new String(nonceChars);
}
}
Customer Service Tel
Business Development
9:00-18:00
Monday-Friday GMT+8
Technical Support
WeChat Pay Global
ICP證