评论

微信支付v3,php代码,包含签名,验签,获取token

微信支付v3获取证书和验签代码,代码简单粗暴,自己也研究的一阵子才搞明白的,有什么不懂的可以留言,微信异步通知需要获取header部分信息来验签,签名验证方式也是一样的

<?php
//设置为常量方便使用
const v3key = '*********';//v3秘钥
const pr = '******';//商户私钥文件apiclient_key.pem
const mchid = '*******';//商户号
const serial_no = '*********';//证书序列号


$url = 'https://api.mch.weixin.qq.com/v3/certificates';
$url_parts = parse_url($url);
$canonical_url = ($url_parts['path'] . (!empty($url_parts['query']) ? "?${url_parts['query']}" : ""));

/*
					  HTTP请求方法\n
					  URL\n
					  请求时间戳\n
					  请求随机串\n
					  请求报文主体\n
GET需要多一个换行符,有body就少一个换行符
*/

            $stime =time();//时间戳
            $nostr = md5(time().mt_rand(000,999));//随机字符串
            $message = "GET"."\n".
            $canonical_url."\n".
            $stime ."\n".
            $nostr ."\n"."\n";
            //var_dump($message);
            
            openssl_sign($message, $sign, file_get_contents(pr), 'sha256WithRSAEncryption');
            $sign = base64_encode($sign);//签名信息进行编码
            $token = sprintf('mchid="%s",nonce_str="%s",timestamp="%d",serial_no="%s",signature="%s"',
            mchid, str_replace("\n","",$nostr), str_replace("\n","",$stime), serial_no, $sign);//格式化字符串
            //var_dump($token,$sign);exit;

            $ch = curl_init();
            curl_setopt($ch,CURLOPT_URL,$url);//设置请求网址
            curl_setopt($ch,CURLOPT_RETURNTRANSFER,1);//返回数据流不输出
            curl_setopt($ch,CURLOPT_HEADER,1);//返回header信息
            curl_setopt($ch,CURLOPT_HTTPHEADER,array('Authorization: WECHATPAY2-SHA256-RSA2048 ' .$token,
            'Accept:application/json',
            'User-Agent:Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36'));//设置header信息
            $crt = curl_exec($ch);
            if (curl_getinfo($ch, CURLINFO_HTTP_CODE) == '200') {
            $headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
            $header = substr($crt, 0, $headerSize);//把header信息单独拿出来
            $body = substr($crt, $headerSize);}//把返回的数据拿出来
            curl_close($ch);
            /*
            echo "<pre>";
            var_dump("这是返回的body:\n".$body);
            echo "<hr>";
            var_dump("这是header数据:\n".$header);
            */
            //var_dump($crt);
            //echo $crt;
            //var_dump( getallheaders());
            $str = get_header($header);//把header信息解析成数组
           
            /*
            echo "<pre>";
            var_dump($str);
            var_dump($body);
            */
            $crs = json_decode($body,true);//把返回的数据解析成数组
            //echo "<pre>";
            $m = $crs['data'][0]['encrypt_certificate']['ciphertext'];//密文,需要解密
            $m = base64_decode($m);//必须需要解码,不然会失败
            $add_data = $crs['data'][0]['encrypt_certificate']['associated_data'];//额外数据
            $nonce = $crs['data'][0]['encrypt_certificate']['nonce'];//解密字符串
            
            //var_dump($crs['data'][0]['encrypt_certificate']['ciphertext']);
           // var_dump($crs);
            $crt = sodium_crypto_aead_aes256gcm_decrypt($m,$add_data,$nonce,v3key);//解密后获取的是证书
            $res = openssl_pkey_get_public($crt);//从证书里面读取公钥资源
            $ttr= openssl_pkey_get_details($res);//解析并获取公钥
            $ttr  = $ttr['key'];//证书公钥
            //var_dump($ttr);
            //下面是验证是否为微信发来的信息
            //var_dump($str);
            $v_sj =$str['Wechatpay-Timestamp']."\n".
            $str['Wechatpay-Nonce']."\n".
            $body."\n";
            //var_dump($v_sj);exit;
            /*
            应答时间戳\n
            应答随机串\n
            应答报文主体\n
            v_sj 是根据返回值生成的数据
            $str['Wechatpay-Signature']是从请求头里面拿出来的数据
            ttr就是证书解析出来的公钥
            sha256WithRSAEncryption 验签方式
            */
            $wx_k = openssl_verify($v_sj,base64_decode($str['Wechatpay-Signature']),$ttr,'sha256WithRSAEncryption');
            if($wx_k === 1){
                echo '数据获取成功,你的微信支付平台证书序列号为:'.$str['Wechatpay-Serial']."<br>"."CR证书为:"."<br>".$crt."<br>"."公共秘钥为:"."<br>".$ttr."<br>"."已为你保存到文件,青岛系统里面进行查看。";
                file_put_contents('wx.crt',$crt);
                file_put_contents('wx.pub',$ttr);
                
            }else{
                echo '验签不通过,所有数据不予展示,请联系管理员';
            }


            
最后一次编辑于  2022-10-25  
点赞 3
收藏
评论

3 个评论

  • 死性不改
    死性不改
    2023-08-08
    你好,完全按照你的这个程序逻辑,微信返回的header 里面的 Wechatpay-Serial和通过接口拿到的平台证书的serial_no 是一致的,使用Sodium扩展sodium_crypto_aead_aes256gcm_decrypt 方法 解密,得到了公钥证书,但是 openssl_verify 这个步骤时 一直返回0,验证不通过,PHP 版本 PHP Version 7.0.33,是自行服务器上按照的sodium扩展包,版本信息如下  
    sodium compiled version	2.0.22
    libsodium headers version	1.0.18
    libsodium library version	1.0.18
    网上找了一些资料
    openssl_verify函数不适用于验证使用sodium_crypto_aead_aes256gcm_decrypt函数解密的数据的签名。这是因为sodium_crypto_aead_aes256gcm_decrypt使用的是Sodium扩展,而openssl_verify函数则用于OpenSSL扩展。这两个扩展的加密和签名算法是不同的,不能直接交叉使用,那应该要如何验证微信 响应的数据的正确性,怎么才能验证签名一致?
    


    2023-08-08
    赞同 1
    回复 1
    • 㐅卝
      㐅卝
      发表于小程序端
      2023-10-26

      验签名使用的是openssl 扩展,解密使用的是另一个扩展,关联是你的业务逻辑是要验证签名还是解密数据。

      2023-10-26
      回复
  • 化清风
    化清风
    2023-07-26

    你好,我生成签名是成功,也拿到了平台证书列表,并且证书列表的序列号和微信返回的header里面的也是一致的(满足验签前提条件之一),组装的验签串也是符合要求,并且也解密了响应体,拿到了公钥,签名也拿到了,但是进行验签时,openssl_verify()返回0.

    2023-07-26
    赞同 1
    回复 1
    • 㐅卝
      㐅卝
      发表于小程序端
      2023-07-29

      是微信给你发送的通知?你进行验签,一直不成功?

      2023-07-29
      2
      回复
  • 月夜秋风
    月夜秋风
    04-15
    i你好,请问文中的$str = get_header($header);//把header信息解析成数组
    get_header()是不是一个自定义函数?
    
    04-15
    赞同
    回复 1
    • 㐅卝
      㐅卝
      发表于小程序端
      04-15

      你好,这是一个自定义函数。

      04-15
      回复
登录 后发表内容