微信支付平台证书是用于微信支付V3的异步回调验签,这个平台证书是V3的接口独有,每隔一段时间就要更新一次,网上大多数都是java或者是臃肿的sdk集成,里面有很多用不到的模块,且不支持php7+以下的版本,因为我平时会帮客户二开一些系统,但是那些系统有些没有这么高的版本,所以就写了这个,现在分享出来。
以下是微信支付V3接口的证书验签流程(图片来自于微信支付官方文档):

以下是代码:
<?php
/**
* LCSAY TENCENT WECHAT PAY V3 微信支付平台证书下载接口(支持php5.6)
* WECHAT AND QQ : 516519782
*/
// 微信支付平台证书下载接口
$platformCertUrl = 'https://api.mch.weixin.qq.com/v3/certificates';
// 商户号
$mchId = '';
// 证书路径
$certPath = '';
// 证书密码
$certPassword = '';
// API 密钥
$apiKey = '';
$serialNumber = getCertificateSerialNumber($certPath, $certPassword); // 商户证书序列号
$privateKey = getcertpkey($certPath, $certPassword); // 商户私钥
// 构建请求头
$headers = [
'Accept: application/json',
'Content-Type: application/json',
'User-Agent:*/*',
'Authorization: WECHATPAY2-SHA256-RSA2048 ' . generateAccessToken(),
];
// 发送请求获取平台证书
//封装curl请求
function curl($platformCertUrl,$headers)
{
$ch = curl_init($platformCertUrl);
curl_setopt($ch, CURLOPT_URL, $platformCertUrl); // 设置请求 URL
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1);
curl_setopt($ch, CURLOPT_HEADER, false);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 0);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 0);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); //设置请求头
return curl_exec($ch);
}
$result = curl($platformCertUrl,$headers);
// 解析返回的证书列表
$certificates = json_decode($result, true);
$certificatesdata = $certificates['data'];
//print_r($certificatesdata);
// 遍历证书列表
foreach ($certificatesdata as $certificate) {
$serialNumber = $certificate['serial_no'];
$ciphertext = $certificate['encrypt_certificate'];
// 解密平台证书
$platformPublicKey = decryptCiphertext($ciphertext,$apiKey);
// TODO: 处理平台证书,可以保存到本地或者使用其它方式进行后续操作
echo "平台证书序列号: " . $serialNumber . "\n";
echo "平台证书公钥: " . $platformPublicKey . "\n";
//如果证书存在 就保存证书 并以序列号命名
if(isset($serialNumber) && isset($platformPublicKey)){
$file = './apiclient_platform_cert.pem';
$fp = fopen($file, 'w'); //w是先清空内容 再写入
fwrite($fp, $platformPublicKey);
fclose($fp);
}
}
//获取私钥证书密钥
function getcertpkey($certPath, $certPassword)
{
$certContent = file_get_contents($certPath);
if (openssl_pkcs12_read($certContent, $certData, $certPassword)){
$certificate = $certData['pkey'];
}else{
echo "Error: Unable to read the cert store.\n";
exit;
}
return $certificate;
}
// 获取证书序列号
function getCertificateSerialNumber($certPath, $certPassword)
{
$certContent = file_get_contents($certPath);
if (openssl_pkcs12_read($certContent, $certData, $certPassword)){
$certificate = $certData['cert'];
}else{
echo "Error: Unable to read the cert store.\n";
exit;
}
$x509 = openssl_x509_read($certificate);
$certInfo = openssl_x509_parse($x509);
///////////////////php获取证书编号没有serialNumberHex只有serialNumber处理方法(php5.6)///////////////////////
///////////////////将 serialNumber 转换成 serialNumberHex///////////////////////
if(isset($certInfo['serialNumberHex'])){
$serialNumberHex = $certInfo['serialNumberHex'];
}else{
$serial = $certInfo['serialNumber'];
$base = bcpow("2", "32");
$counter = 100;
$res = "";
$val = $serial;
while($counter > 0 && $val > 0) {
$counter = $counter - 1;
$tmpres = dechex(bcmod($val, $base)) . "";
/* adjust for 0's */
for ($i = 8-strlen($tmpres); $i > 0; $i = $i-1) {
$tmpres = "0$tmpres";
}
$res = $tmpres .$res;
$val = bcdiv($val, $base);
}
if ($counter <= 0) {
echo 'Occured failed.';
exit;
}
$serialNumberHex = strtoupper($res);
}
return $serialNumberHex;
}
// 生成访问令牌
function generateAccessToken()
{
global $mchId, $serialNumber, $privateKey, $platformCertUrl;
$timestamp = time();
$nonce = md5(mt_rand());
$message = "GET\n" . parse_url($platformCertUrl, PHP_URL_PATH) . "\n" . $timestamp . "\n" . $nonce . "\n\n";
$signature = '';
openssl_sign($message, $signature, $privateKey, OPENSSL_ALGO_SHA256);
$signature = base64_encode($signature);
return "mchid=\"$mchId\",nonce_str=\"$nonce\",timestamp=\"$timestamp\",serial_no=\"$serialNumber\",signature=\"$signature\"";
}
// 解密平台证书
function decryptCiphertext($ciphertext,$apiKey)
{
global $privateKey;
// 将 ciphertext 参数进行 Base64 解码
$ciphertextcip = base64_decode($ciphertext['ciphertext']);
$associatedData = $ciphertext['associated_data'];
$nonce = $ciphertext['nonce'];
// 解密数据
$AUTH_TAG_LENGTH_BYTE = 16;
if (strlen($ciphertextcip) <= $AUTH_TAG_LENGTH_BYTE) {
return false;
}
$ctext = substr($ciphertextcip, 0, -$AUTH_TAG_LENGTH_BYTE);
$authTag = substr($ciphertextcip, -$AUTH_TAG_LENGTH_BYTE);
return openssl_decrypt($ctext, 'aes-256-gcm', $apiKey, OPENSSL_RAW_DATA, $nonce, $authTag, $associatedData);
}
?>
评论0