微信支付平台证书是用于微信支付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