数据签名
微信支付目前现行两大类数据签名方法,对称算法基于MD5及HMAC-SHA256数据摘要算法,非对称算法基于RSA-OAEP(模数2048)算法。
对称算法
签名生成的通用步骤如下:
第一步:
设所有发送或者接收到的数据为集合M,将集合M内非空参数值的参数按照参数名ASCII码从小到大排序(字典序),使用URL键值对的格式(即key1=value1&key2=value2…)拼接成字符串stringA。
特别注意以下重要规则:
- 参数名ASCII码从小到大排序(字典序);
- 如果参数的值为空不参与签名;
- 参数名区分大小写;
- 验证调用返回或微信主动通知签名时,传送的sign参数不参与签名,将生成的签名与该sign值作校验;
- 微信支付接口可能增加字段,验证签名时必须支持增加的扩展字段;
第二步:
在stringA最后拼接上key得到stringSignTemp字符串,并对stringSignTemp进行MD5/HMAC-SHA256运算,再将得到的字符串所有字符转换为大写,得到sign值signValue。
特别注意:
- 密钥key的长度为32个字节;
- key设置路径:微信商户平台 -> 账户中心 -> 账户设置 -> API安全 -> 设置API密钥;
微信支付API接口协议中包含字段nonce_str,主要保证签名不可预测。微信支付官方推荐生成随机数算法如下:调用随机数函数生成,将得到的值转换为字符串。
以官方文档举的例子,用PHP
实现:
后台数据交换
设传送的参数 集合 $collection 如下:
$collection = [
'appid' => 'wxd930ea5d5a258f4f',
'mch_id' => '10000100',
'device_info' => '1000',
'body' => 'test',
'nonce_str' => 'ibuaiVcKdpRxkhJA'
];
第一步:按照参数名ASCII字典序并对参数按照URL键值对的格式拼接如下:
$stringA = 'appid=wxd930ea5d5a258f4f&body=test&device_info=1000&mch_id=10000100&nonce_str=ibuaiVcKdpRxkhJA';
第二步:拼接API密钥key:
use WeChatPay\Crypto\Hash;
//注:key为商户平台设置的密钥key
$stringSignTemp = $stringA . '&key=192006250b4c09247ec02edce69f6a2d';
//注:MD5签名方式
$sign = \strtoupper(Hash::md5($stringSignTemp));
//样本值='9A0A8659F005D6984697E2CA0A9CF3B7'
//注:HMAC-SHA256签名方式,部分语言的hmac方法生成结果二进制结果,需要调对应函数转化为十六进制字符串。
$sign = \strtoupper(Hash::hmac($stringA, $key, 'sha256'));
//样本值='6A9AE1657590FD6257D693A078E1C3E4BB6BA4DC30B23E0EE2496E54170DACD6'
则传输的数据即为:
$collection = [
'appid' => $appId,
'mch_id' => $mchId,
'device_info' => '1000',
'body' => 'test',
'nonce_str' => 'ibuaiVcKdpRxkhJA',
//以MD5为例
'sign' => '9A0A8659F005D6984697E2CA0A9CF3B7'
];
APIv2是以XML
格式作为数据交换方式,则需转换上述数据为XML
文本格式如下:
<xml>
<appid>wxd930ea5d5a258f4f</appid>
<mch_id>10000100</mch_id>
<device_info>1000</device_info>
<body>test</body>
<nonce_str>ibuaiVcKdpRxkhJA</nonce_str>
<sign>9A0A8659F005D6984697E2CA0A9CF3B7</sign>
</xml>
以上签名及数据格式转换过程,用本开发包即如下:
// 对集合M按参数名字典序排列 -> URL风格拼接数据 -> 数据签名并把值大写
use WeChatPay\Formatter;
use WeChatPay\Transformer;
use WeChatPay\Crypto\Hash;
// 计算签名并把签名值大写
$collection['sign'] = \strtoupper(Hash::md5(
// URL风格拼接数据
Formatter::queryStringLike(
// 按参数名字典序排列
Formatter::ksort($collection)
),
$key
));
// 或者使用封装的`Hash::sign`方法简化如下
$collection['sign'] = Hash::sign(Hash::ALGO_MD5, $collection, $key);
// 转换为`XML`文本
$xml = Transformer::toXml($collection);
前台数据交换
此种方式也遵循 通用步骤 原则,只是 集合 $collection 按业务表现不同而不同,归纳如下:
现金支付
JSAPI 唤起微信支付收银台场景
use WeChatPay\Formatter;
use WeChatPay\Crypto\Hash;
$nonceStr = Formatter::nonce();
$timeStamp = (string) Formatter::timestamp();
$package = 'prepay_id=' . $prepayId;
// previousSignType 即后台调用 unifiedorder 接口时的签名方法
$signType = $previousSignType ?? Hash::ALGO_MD5;
$collection = [
'appId' => $appId,
'timeStamp' => $timeStamp,
'nonceStr' => $nonceStr,
'package' => $package,
'signType' => $signType
];
$collection['paySign'] = Hash::sign($signType, $collection, $key);
echo \json_encode($collection);
APP 唤起微信支付收银台场景
use WeChatPay\Formatter;
use WeChatPay\Crypto\Hash;
$nonceStr = Formatter::nonce();
$timeStamp = (string) Formatter::timestamp();
$package = 'Sign=WXPay';
// previousSignType 即后台调用 unifiedorder 接口时的签名方法
$signType = $previousSignType ?? Hash::ALGO_MD5;
$collection = [
'appid' => $appId,
'partnerid' => $mchId,
'timestamp' => $timeStamp,
'noncestr' => $nonceStr,
'package' => $package,
'prepayid' => $prepayId
];
$collection['sign'] = Hash::sign($signType, $collection, $key);
echo \json_encode($collection);
小程序红包 特殊规则
微信小程序 唤起发放小程序红包场景
use WeChatPay\Formatter;
use WeChatPay\Crypto\Hash;
$nonceStr = Formatter::nonce();
$timeStamp = (string) Formatter::timestamp();
// 签名方法为固定值
$signType = Hash::ALGO_MD5;
$collection = [
'appId' => $appId,
'timeStamp' => $timeStamp,
'nonceStr' => $nonceStr,
// $package 即后台调用 sendminiprogramhb 接口的 package 返回字符串
'package' => \urlencode($package)
];
$collection['paySign'] = Hash::md5(
Formatter::queryStringLike(
Formatter::ksort($collection)
), $key
);
$collection['signType'] = $signType;
echo \json_encode($collection);
代金券/商家券
小程序发券插件
use WeChatPay\Crypto\Hash;
$sendCouponParams = [[
'stock_id' => '123',
'out_request_no' => 'R123'
], [
'stock_id' => '456',
'out_request_no' => 'R456'
]];
$collection = ['send_coupon_merchant' => $sendCouponMerchant];
foreach ($sendCouponParams as $index => $coupon) {
foreach ($coupon as $key => $value) {
$collection[$key . $index] = $value;
}
}
// 签名方法为固定值
$signType = Hash::ALGO_HMAC_SHA256;
$collection['sign'] = Hash::sign($signType, $collection, $key);
echo \json_encode($collection);
通过Url跳转,由商户H5重定向至指定微信支付H5页面
use WeChatPay\Crypto\Hash;
use GuzzleHttp\Psr7\Uri;
use GuzzleHttp\Psr7\Query;
// 签名方法为固定值
$signType = Hash::ALGO_HMAC_SHA256;
$collection = [
'stock_id' => $stockId,
'out_request_no' => $outRequestNo,
'send_coupon_merchant' => $sendCouponMerchant,
'open_id' => $openId,
'coupon_code' => $couponCode
];
$collection['sign'] = Hash::sign($signType, $collection, $key);
$actionUrl = 'https://action.weixin.qq.com/busifavor/getcouponinfo';
$redirectUrl = (string) (new Uri($actionUrl))
->withQuery(Query::build($collection))
->withFragment('wechat_pay&wechat_redirect');
微信支付分
APP 唤起微信支付分小程序确认订单场景
use WeChatPay\Formatter;
use WeChatPay\Crypto\Hash;
use GuzzleHttp\Psr7\Query;
$nonceStr = Formatter::nonce();
$timeStamp = (string) Formatter::timestamp();
// 签名方法为固定值
$signType = Hash::ALGO_HMAC_SHA256;
$collection = [
'mch_id' => $mchId,
'package' => $package,
'timestamp' => $timeStamp,
'nonce_str' => $nonceStr,
'sign_type' => $signType
];
$collection['sign'] = Hash::sign($signType, $collection, $key);
$data = [
'businessType' => 'wxpayScoreUse',
'query' => Query::build($collection),
'extInfo' => [
'miniProgramType' => 0
]
];
echo \json_encode($data);
APP 唤起微信支付分小程序订单详情场景
use WeChatPay\Formatter;
use WeChatPay\Crypto\Hash;
use GuzzleHttp\Psr7\Query;
$nonceStr = Formatter::nonce();
$timeStamp = (string) Formatter::timestamp();
// 签名方法为固定值
$signType = Hash::ALGO_HMAC_SHA256;
$collection = [
'mch_id' => $mchId,
'service_id' => $serviceId,
'out_order_no' => $outOrderNo,
'timestamp' => $timeStamp,
'nonce_str' => $nonceStr,
'sign_type' => $signType
];
$collection['sign'] = Hash::sign($signType, $collection, $key);
$data = [
'businessType' => 'wxpayScoreDetail',
'query' => Query::build($collection),
'extInfo' => [
'miniProgramType' => 0
]
];
echo \json_encode($data);
JSAPI 唤起微信支付分小程序确认订单场景
use WeChatPay\Formatter;
use WeChatPay\Crypto\Hash;
use GuzzleHttp\Psr7\Query;
$nonceStr = Formatter::nonce();
$timeStamp = (string) Formatter::timestamp();
// 签名方法为固定值
$signType = Hash::ALGO_HMAC_SHA256;
$collection = [
'mch_id' => $mchId,
'package' => $serviceId,
'timestamp' => $timeStamp,
'nonce_str' => $nonceStr,
'sign_type' => $signType
];
$collection['sign'] = Hash::sign($signType, $collection, $key);
$data = [
'businessType' => 'wxpayScoreUse',
'queryString' => Query::build($collection)
];
echo \json_encode($data);
JSAPI 唤起微信支付分小程序订单详情场景
use WeChatPay\Formatter;
use WeChatPay\Crypto\Hash;
use GuzzleHttp\Psr7\Query;
$nonceStr = Formatter::nonce();
$timeStamp = (string) Formatter::timestamp();
// 签名方法为固定值
$signType = Hash::ALGO_HMAC_SHA256;
$collection = [
'mch_id' => $mchId,
'service_id' => $serviceId,
'out_order_no' => $outOrderNo,
'timestamp' => $timeStamp,
'nonce_str' => $nonceStr,
'sign_type' => $signType
];
$collection['sign'] = Hash::sign($signType, $collection, $key);
$data = [
'businessType' => 'wxpayScoreDetail',
'queryString' => Query::build($collection)
];
echo \json_encode($data);
微信小程序 唤起微信支付分小程序确认订单场景
use WeChatPay\Formatter;
use WeChatPay\Crypto\Hash;
$nonceStr = Formatter::nonce();
$timeStamp = (string) Formatter::timestamp();
// 签名方法为固定值
$signType = Hash::ALGO_HMAC_SHA256;
$collection = [
'mch_id' => $mchId,
'package' => $serviceId,
'timestamp' => $timeStamp,
'nonce_str' => $nonceStr,
'sign_type' => $signType
];
$collection['sign'] = Hash::sign($signType, $collection, $key);
$data = [
'businessType' => 'wxpayScoreUse',
'extraData' => $collection
];
echo \json_encode($data);
微信小程序 唤起微信支付分小程序订单详情场景
use WeChatPay\Formatter;
use WeChatPay\Crypto\Hash;
$nonceStr = Formatter::nonce();
$timeStamp = (string) Formatter::timestamp();
// 签名方法为固定值
$signType = Hash::ALGO_HMAC_SHA256;
$collection = [
'mch_id' => $mchId,
'service_id' => $serviceId,
'out_order_no' => $outOrderNo,
'timestamp' => $timeStamp,
'nonce_str' => $nonceStr,
'sign_type' => $signType
];
$collection['sign'] = Hash::sign($signType, $collection, $key);
$data = [
'businessType' => 'wxpayScoreDetail',
'extraData' => $collection
];
echo \json_encode($data);
非对称算法
此种签名方法,官方文档介绍 已经很明晰,这里不再细述规则,仅做实现介绍如下:
后台数据交换
请求数据签名,用本开发包如下:
use WeChatPay\Formatter;
use WeChatPay\Crypto\Rsa;
/** @var string 'GET'|'POST'|'PUT'|'PATCH'|'DELETE' */
$httpMethod;
/** @var string */
$pathname;
/** @var string */
$body;
/** @var \OpenSSLAsymmetricKey|resource|string 商户API私钥实例 */
$merchantPrivateKeyInstance;
Rsa::sign(
Formatter::request(
$httpMethod,
$pathname,
(string) Formatter::timestamp(),
Formatter::nonce(),
$body
),
$merchantPrivateKeyInstance
);
返回值数据验签,用本开发包如下:
use WeChatPay\Formatter;
use WeChatPay\Crypto\Rsa;
/** @var string 请求返回的头'wechatpay-serial'值 */
$serial;
/** @var string 请求返回的头'wechatpay-timestamp'值 */
$timestamp;
/** @var string 请求返回的头'wechatpay-nonce'值 */
$nonce;
/** @var string 请求返回的头'wechatpay-signature'值 */
$signature;
/** @var string 请求返回的body */
$body;
/** @var array 微信支付平台证书/平台公钥键值对 */
$platformPublickKeyMap;
Rsa::verify(
Formatter::response(
$timestamp,
$nonce,
$body
),
$signature,
$platformPublickKeyMap[$serial]
);
前台数据交换
JSAPI 唤起微信支付收银台场景
use WeChatPay\Formatter;
use WeChatPay\Crypto\Rsa;
/** @var \OpenSSLAsymmetricKey|resource|string 商户API私钥实例 */
$merchantPrivateKeyInstance;
$nonceStr = Formatter::nonce();
$timeStamp = (string) Formatter::timestamp();
$package = 'prepay_id=' . $prepayId;
$data = [
'appId' => $appId,
'timeStamp' => $timeStamp,
'nonceStr' => $nonceStr,
'package' => $package,
'signType' => 'RSA',
'paySign' => Rsa::sign(
Formatter::joinedByLineFeed($appId, $timeStamp, $nonceStr, $package),
$merchantPrivateKeyInstance
)
];
echo \json_encode($data);
APP 唤起微信支付收银台场景
use WeChatPay\Formatter;
use WeChatPay\Crypto\Rsa;
/** @var \OpenSSLAsymmetricKey|resource|string 商户API私钥实例 */
$merchantPrivateKeyInstance;
$noncestr = Formatter::nonce();
$timestamp = (string) Formatter::timestamp();
$data = {
'appid' => $appId,
'partnerid' => $mchId,
'prepayid' => $prepayId,
'package' => 'Sign=WXPay',
'timestamp' => $timestamp,
'noncestr' => $noncestr,
'sign' => Rsa::sign(
Formatter::joinedByLineFeed($appId, $timestamp, $noncestr, $prepayid),
$merchantPrivateKeyInstance
)
};
echo \json_encode($data);