微信小微商户下载平台证书接口(PHP SHA256 with RSA 签名,AEAD_AES_256_GCM解密方法)

一、序言

最近在做微信小微商户接口对接,对接里面的下载平台证书接口中遇到的坑在这记录下。

二、资料

1、《1.1. 下载平台证书接口(v5.1)》 查看


二、正文

小微商户申请入驻接口中有几个参数是需要先调用下载证书接口的,所以我们现在先看看下载证书接口。小微商户接口PHP相关的示例代码,网上也找不到什么例子。一个人研究这个下载证书接口也踩了好多坑,现在发出来,希望之后做这个接口的人百度的时候有个参考。
微信小微商户下载平台证书接口(PHP SHA256 with RSA 签名,AEAD_AES_256_GCM解密方法)_第1张图片
当我请求接口时一直返回签名失败,对着文档看了几遍都没发现错在哪。 看下图计算签名值方法中构造待签名串这个地方,我按他的第一步第二步来拼接串,后来发现他最下面最终的拼接不是按这个第一步第二步来的。最终的位置应该是第四步请求时间戳和第三步请求随机串应该是调换位置的。然后拿这个值去生成签名就OK了。
微信小微商户下载平台证书接口(PHP SHA256 with RSA 签名,AEAD_AES_256_GCM解密方法)_第2张图片
接口中主要是没有PHP示例,而且平时很少用到文档中的加密签名解密等方法,一时有点懵逼。后来经过一番查找文档手册,在 php.net 中找到了答案。具体写法我就不一一赘述,大家可看下下面贴出的容易采坑几个方法,加密方法里面的函数有兴趣的可以自行去百度。


三、下载证书接口难点示例

(1) 准备接口中使用到的加密等方法

/**
 * getRandChar 获取随机字符串
 * @param int $length
 * @return mixed
 */
abstract protected function getRandChar($length = 32);

/**
 * setHashSign SHA256 with RSA 签名
  * @param $signContent
  * @return string
  */
 protected function encryptSign($signContent)
 {
     // 解析 key 供其他函数使用。
     $privateKey = openssl_get_privatekey($this->getPrivateKey());
     // 调用openssl内置签名方法,生成签名$sign
     openssl_sign($signContent, $sign, $privateKey, "SHA256");
     // 释放内存中私钥资源
     openssl_free_key($privateKey);
     $sign = base64_encode($sign);
     return $sign;
 }

(2)curl请求下载证书接口

/**
 * getCertificates  下载平台证书
 * @return mixed
 */
 public function downloadCertificates()
 {
     try {
         $url = self::WXAPIHOST . 'v3/certificates';
         // 请求随机串
         $nonce_str = $this->getRandChar();
         // 当前时间戳
         $timestamp = time();
         // 签名串
         $signContent = "GET\n/v3/certificates\n" . $timestamp . "\n" . $nonce_str . "\n\n";
         // 签名值
         $signature = $this->encryptSign($signContent);
         // 含有服务器用于验证商户身份的凭证
         $authorization  = 'WECHATPAY2-SHA256-RSA2048 mchid="' . $this->mch_id . '",nonce_str="' . $nonce_str . '",signature="' . $signature . '",timestamp="' . $timestamp . '",serial_no="' . $this->serial_no . '"';
         $curl_v         = curl_version();
         $header         = [
             'Accept:application/json',
             // 'Accept-Language:zh-CN',    // 默认 zh-CN 可以不填
             'Authorization:' . $authorization,
             'Content-Type:application/json',
             'User-Agent:curl/' . $curl_v['version'],
         ];
         $result         = $this->httpsRequest($url, NULL, $header);
         $responseHeader = $this->parseHeaders($result[2]);
         $http_code      = $result[1];
         $responseBody   = json_decode($result[0], true);
         if ($http_code == 200 && !isset($responseBody['code'])) {
             return $this->verifySign($responseHeader, $result[0]);
         } else {
             throw new \Exception($responseBody['code'] . '----' . $responseBody['message']);
         }
     } catch (\Exception $e) {
         throw new WxException($e->getCode());
     }
 }

(3)curl需要获取响应头然后校验响应头里面的签名以及解密返回的密文证书

/**
 * verifyHashSign 校验签名
 * @param $data
 * @param $signature
 * @return int
 */
protected function verifySign($responseHeader, $responseBody)
{
    $last_data = $this->newResponseData();
    $new_data  = json_decode($responseBody, true);
    $one       = false;
    if (empty($last_data)) {
        // 没有获取到上一次保存在本地的数据视为第一请求下载证书接口
        $serial_no = $this->getNewCertificates($new_data['data']);
        $one       = true;
    } else {
        $serial_no = $last_data['serial_no'];
    }
    // 注 1:微信支付平台证书序列号位于 HTTP 头`Wechatpay-Serial`,验证签名前请先检查序列号是否跟商户所持有的微信支付平台证书序列号一致。(第一次从 1.1.5.中回包字段 serial_no 获取,非第一次时使用上次本地保存的平台证书序列号)
    if ($serial_no != $responseHeader['Wechatpay-Serial']) {
        if ($one)
            $this->clearFile('newResponseData');
        return 0;
    }
    $publicKey = $this->getPublicKey();
    if ($publicKey) {
        // 用微信支付平台证书公钥(第一次下载平台证书时从 1.1.5.中 “加密后的证书内容”进行解密获得。非第一次时使用上次本地保存的公钥)对“签名串”进行 SHA256 with RSA 签名验证
        $data              = $this->signatureValidation($responseHeader, $responseBody);
        $signature         = base64_decode($responseHeader['Wechatpay-Signature']);
        $publicKeyResource = openssl_get_publickey($publicKey);
        $f                 = openssl_verify($data, $signature, $publicKeyResource, "SHA256");
        openssl_free_key($publicKeyResource);
        if ($f == 1 && !empty($last_data)) {
            // 获取弃用日期最长证书
            return $this->getNewCertificates($new_data['data'], $last_data);
        }
        return $f;
    } else {
        return 0;
    }
}

/**
 * decryptCiphertext  AEAD_AES_256_GCM 解密加密后的证书内容得到平台证书的明文
 * @param $ciphertext
 * @param $ad
 * @param $nonce
 * @return string
 */
protected function decryptCiphertext($data)
{
    $encryptCertificate = $data['encrypt_certificate'];
    $ciphertext         = base64_decode($encryptCertificate['ciphertext']);
    $associated_data    = $encryptCertificate['associated_data'];
    $nonce              = $encryptCertificate['nonce'];
    // sodium_crypto_aead_aes256gcm_decrypt >=7.2版本,去php.ini里面开启下libsodium扩展就可以,之前版本需要安装libsodium扩展,具体查看php.net(ps.使用这个函数对扩展的版本也有要求哦,扩展版本 >=1.08)
    $plaintext          = sodium_crypto_aead_aes256gcm_decrypt($ciphertext, $associated_data, $nonce, $this->aes_key);
    $this->savePublicKey($plaintext);
    $this->newResponseData($data);
    return true;
}

四、结束语

其实这个接口本身不难,主要是其中使用的PHP SHA256 with RSA 签名、AEAD_AES_256_GCM解密方法等之前没有接触过,而网上相关文章又是比较少,所以做的过程中踩了不少坑。希望看到本篇文章能够帮助在看的朋友少踩点坑。有疑问可以在下面评论区评论留言


2018-11-06 新增说明:貌似微信的下载证书文档不知道啥时候改版了,上面也给出了php的示例。所以,大家参考这篇文章的同时记得以最新文档为准。当然,开启 sodium 扩展的方法不知道的话可以看我另外一篇文章PHP 项目使用 libsodium 扩展

2019-03-19 最新下载平台证书接口已经更新,进入查看WechatXiaowei/V1/Services/Traits/Certificate.php > downloadCertificates方法

2019-05-24 项目github地址 https://github.com/wannanbigpig/WechatXiaowei ,文中没有写出来的其他代码完整的都在github上面

2019年6月13日更新


小微商户composer扩展包功能已经上线
github:overtrue/wechat
文档:EasyWeChat / 小微商户

你可能感兴趣的:(PHP,微信开发)