1. 程式人生 > >微信小微商戶下載平臺證書介面(PHP SHA256 with RSA 簽名,AEAD_AES_256_GCM解密方法)

微信小微商戶下載平臺證書介面(PHP SHA256 with RSA 簽名,AEAD_AES_256_GCM解密方法)

一、序言

最近在做微信小微商戶介面對接,對接裡面的下載平臺證書介面中遇到的坑在這記錄下。

二、資料

1、《1.1. 下載平臺證書介面(v5.1)》 檢視


二、正文

小微商戶申請入駐介面中有幾個引數是需要先呼叫下載證書介面的,所以我們現在先看看下載證書介面。小微商戶介面PHP相關的示例程式碼,網上也找不到什麼例子。一個人研究這個下載證書介面也踩了好多坑,現在發出來,希望之後做這個介面的人百度的時候有個參考。
這裡寫圖片描述
當我請求介面時一直返回簽名失敗,對著文件看了幾遍都沒發現錯在哪。 看下圖計算簽名值方法中構造待簽名串這個地方,我按他的第一步第二步來拼接串,後來發現他最下面最終的拼接不是按這個第一步第二步來的。最終的位置應該是第四步請求時間戳和第三步請求隨機串應該是調換位置的。然後拿這個值去生成簽名就OK了。
計算簽名值


介面中主要是沒有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 擴充套件