微信API V3 平台证书解密失败,返回false,如何解决?

安装官方文档说明的,要先获取平台证书列表,然后再进行解密,获取方法如下:

微信API V3 平台证书解密
图片中User-Agent后面备注的用户代理(https://zh.wikipedia.org/wiki/User_agent)不知道是干嘛的..

按照之前的签名方法,最后获得Authiorization值,放到header头中,header头参数如下:

$headers[] = 'Content-Type:application/json';
$headers[] = 'Accept:application/json';
$headers[] = 'User-Agent:'.$_SERVER['HTTP_USER_AGENT'];
$headers[] = 'Authorization:WECHATPAY2-SHA256-RSA2048 '.$token;

获得证书列表如下:
微信API V3 平台证书解密

为方便后面说明问题,我这里把data数据整个贴出来

{
    "data": [
        {
            "effective_time": "2020-08-20T14:59:52+08:00",
            "encrypt_certificate": {
                "algorithm": "AEAD_AES_256_GCM",
                "associated_data": "certificate",
                "ciphertext": "io6eoAWJH1rb8GJ04aJ6LBUu059j2VhWpoi3Odaez1ngOHVdNPH4bHRLRji6Bdb3QTYWgF0L9Zjdjc/Zbt5dC10ixd8KlB1ZvRDaJ4nSSmVdJ0L92i3ORHJmKHsWVbt8+ZaM2Kg+vfDLRqnoZ4/YnL4PPi/U5bXovvFIzvOcvb6u6hXdq8+Ad7Ms4iOilKJyOiDNMci7HlC+h4elp5UvbebITMEG4OdzexHi8PEcrcRYEODFygzd0SDvOj5sQGjRTqtpA71o55gD0AKTtud3SH856y/N/2GIcmHArB7U5Q5zXQQQ8Fz14KA5bas7snqMMlqv/6c4aFsQjA1Di2Dm1+/E2aMHWn4UpDAohV4X5+MSHHfDdtsIfnvsgKwLjzTKXSe2d/FEjPYgtf/p6GwptGTJFbrk4octETAMpVIE8c2lUq6E8ekf+PqCYLT5njin8z6CBK/7PC4DrR195vTf/pOikyEV6fqc3/iety39y2LCgvDzL/0rz40thvHz4uBNqaxtnTo2SJlouR4D3qi+dmjO4XMWyPAZXpxxboV9PCQAufSGF65tSO8pRGPZiD76mNCyRi8nYeZtL3HFg4afXZeObAzQMJnxaMRsn7jObS+pibgbK45oUfmuoq8yEBUuvqvf5O3JPqKyI4QVqmYg0H07U9ueCWR/iHlsEsSwMG9Hjy4SvW5wPGYGdW9mio21YOl1uucTkS2b+szILnSaJ3oWoTb417qjoGI6Bt6ZWRRZAOmJsJGkzDTv1din0Wg/vRUpFFS/HI466E6yjEbORC8sPL1oze9qx2KsgML6GcUABdWcUXXqzScXxhFufeIkdqj2hG+I+ElNwlpuY/FKd6pqM29BP5up+EtazsABMjEb3e+WHbrwzmzY5c+uRwozd/Z15SKjYtgzOTK0HKbmyOBccA+5moFIt42Alwci3UIUYc5KStdywT42iCc++FtE+Dzu5R0spFFnEj9g9gbw8HzWWL2SCHoYOSsyFegz1QGYiJYAILdIle0IyFD2+PZGNbcdXzE6y2VS6GREhnRmn6QMfb83L7xUTQQf43NdsgXLb+XHGwtn0tZqH+vsMKn1hi0wmelqIsxHv+CvFKg/f7HoRd52vZ6i+uIfXE2nR1dHbygMtlZan47D45g3ZT+lI+0ECojFQI9IuGxRP35n3Sv4yjEvSIbs3+BTEYWZs6gmCyyK2vIm58j8TkTtuyGC5s4ExKl2o3Qfw1mUUg6WKzfnc4iup9Q8JsRNyH+/ra4FvATODvRZakAqlsaV079E+FRu73D+4lq92ZBST/XnGW2jrxuOxGsI2Q9AHiOZWoIFXVZp8DtBaFj/hGEAgN0atv53At/KdrM5Y+x7zx6v9NGOKkFnkw7LuJsARwj6pVSoh2cOQ9tJzCeeSQF985+q66Bmq6Kq51oMnyEzMe+HoOxeUEcTvLfCwteAcJzG/AkZwkwvnXLCcRBpc4ZEEfJtzwSasqdYapE/jexHG1fCQXcuqtdJUeXtxIBBMeTwLEBwed6pGjNw1tmDyNRN2fOzPX7FXbjgek8CJZC4oSStp7tuMIYkoV0RbmDBiSXV0QdOQ5vXgZw4WPalWv32gYhXG8yg8EE6MbPP4Pl8TpFm1CfCpWz2MVS5PyzY/2+lMfGuE3QK5IURbbZsILZJSY6e9TS9stquZEm4hnYB1syzTIf2+dL3GoFOQ45risIUcO68pTo4uBvKm2elpYTtiiQuwxj+0aKwLAilCGy3khNEdZfreWQaTuM///Pu5CkuBbCCYoevJEQnvX7U1HWRDJ1BjVE5Dqc23m2ThQ4exgihvikA4D2RnvGMpBhSJ71kD1kuPMCw+pQLvPFTKS/lOsH9cDZR+Gulvk29TTZ01EtjaQCbwrn6YQ==",
                "nonce": "aacc84fbf399"
            },
            "expire_time": "2025-08-19T14:59:52+08:00",
        }
    ]
}

下面开始看官方文档里证书和回调报文解密的部分,看到官方有提供整个解密操作的php代码,这里也全部贴出来,如下:

class AesUtil{
  /**
    * AES key
    *
    * @var string
    */
  private $aesKey;

  const KEY_LENGTH_BYTE = 32;
  const AUTH_TAG_LENGTH_BYTE = 16;

  /**
    * Constructor
    */
  public function __construct($aesKey)
  {
      if (strlen($aesKey) != self::KEY_LENGTH_BYTE) {
          throw new InvalidArgumentException('无效的ApiV3Key,长度应为32个字节');
      }
      $this->aesKey = $aesKey;
  }

  /**
    * Decrypt AEAD_AES_256_GCM ciphertext
    *
    * @param string    $associatedData     AES GCM additional authentication data
    * @param string    $nonceStr           AES GCM nonce
    * @param string    $ciphertext         AES GCM cipher text
    *
    * @return string|bool      Decrypted string on success or FALSE on failure
    */
  public function decryptToString($associatedData, $nonceStr, $ciphertext)
  {
      $ciphertext = \base64_decode($ciphertext);
      if (strlen($ciphertext) <= self::AUTH_TAG_LENGTH_BYTE) {
          return false;
      }

      // ext-sodium (default installed on >= PHP 7.2)
      if (function_exists('\sodium_crypto_aead_aes256gcm_is_available') &&
          \sodium_crypto_aead_aes256gcm_is_available()) {
          return \sodium_crypto_aead_aes256gcm_decrypt($ciphertext, $associatedData, $nonceStr, $this->aesKey);
      }

      // ext-libsodium (need install libsodium-php 1.x via pecl)
      if (function_exists('\Sodium\crypto_aead_aes256gcm_is_available') &&
          \Sodium\crypto_aead_aes256gcm_is_available()) {
          return \Sodium\crypto_aead_aes256gcm_decrypt($ciphertext, $associatedData, $nonceStr, $this->aesKey);
      }

      // openssl (PHP >= 7.1 support AEAD)
      if (PHP_VERSION_ID >= 70100 && in_array('aes-256-gcm', \openssl_get_cipher_methods())) {
          $ctext = substr($ciphertext, 0, -self::AUTH_TAG_LENGTH_BYTE);
          $authTag = substr($ciphertext, -self::AUTH_TAG_LENGTH_BYTE);

          $rs = \openssl_decrypt($ctext, 'aes-256-gcm', $this->aesKey, \OPENSSL_RAW_DATA, $nonceStr,
              $authTag, $associatedData);
          var_dump($rs);exit;  // 打印出解密结果
          return $rs;
      }

      throw new \RuntimeException('AEAD_AES_256_GCM需要PHP 7.1以上或者安装libsodium-php');
  }
}

这个类很简单,主要是用到3个参数:$associatedData, $nonceStr, $ciphertext,还有一个初始化类的时候用到的API V3的秘钥,这个是在商户后台的API安全里配置的。
我直接把官方提供的这个类原封不动的拷过来,然后在控制器方法中引用这个类:

$aesutil = new AesUtilController($apiv3Key);
$rs = $aesutil->decryptToString($associatedData,$nonce,$ciphertext);

四个参数:
$apiv3Key 我在商户后台-API安全里配置的秘钥;
$associatedData 就是上面data数据里的associated_data值”certificate”;
$nonce 就是上面data数据里的nonce值”aacc84fbf399”;
$ciphertext 就是上面data数据里的nonce值”ciphertext”;

全部配置好后,运行测试,返回bool(false),解密失败!

想不明白,问题出在哪里,一共就4个参数,$apiv3Key是固定值,不会有问题,其他3个参数都是从平台api获取的,应该也不会错的啊?
尝试在线base64解码”ciphertext”的值,结果是乱码:

微信API V3 平台证书解密失败,返回false,如何解决?
一脸懵逼!不知道是本来就应该是乱码还是我获取的”ciphertext”值是有问题的?又或者是我中间漏了什么操作?
又研究了一下官方的代码,看到sodium_crypto_aead_aes256gcm_decrypt这个方法不能使用,于是又百度,看到需要开启php.ini里的extension=php_sodium.dll,发现里面没有这行代码,于是直接写进去了,再到php安装目录下查看libsodium.dll文件是存在的,再到ext目录下检查php_sodium.dll文件也在存在的,最后phpinfo(),看到sodium配置成功了。

再次尝试运行,仍然返回bool(false),解密还是失败!

至此,我基本是没得办法了。。

本作品采用《CC 协议》,转载必须注明作者和本文链接
《L01 基础入门》
我们将带你从零开发一个项目并部署到线上,本课程教授 Web 开发中专业、实用的技能,如 Git 工作流、Laravel Mix 前端工作流等。
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。
讨论数量: 8

晚上经过我苦思冥想加测试,毫无悬念都是返回false!

最后我实在无计可施了,想着,要不把这API V3的秘钥换一下吧?

于是,我到商户后台就重设了一个秘钥,然后再次尝试。。

竟然成功返回秘钥了!我特么炸了啊!!

我之前那个API V3明明就是初次设置的,用的MD5随便生成的字串,当时就记下了秘钥值,而且之后一直没有动过,为什么?? 难道是因为生成的都是小写字母? 还是我当时记录时就弄错了秘钥?

算了,不管怎样,错了就是错了,还是要在这里分享一下,大家引以为戒吧…

3年前 评论

之前的秘钥全小写对接接口就失败,秘钥要包含大小写字母数字才行,全小写之前的版本会签名错误,v3应该也是要大小写字母数字 :joy:

3年前 评论
葱香小油条 (作者) 3年前
acvc225 (楼主) 3年前

我也被微信这个bug坑过无数次了。很多时候重置一下密钥就可以了。

3年前 评论
acvc225 (楼主) 3年前

重置了APIv3密钥(添加了大写字母),可以解密,微信真坑啊

2年前 评论

我也是这个问题,幸亏有前人踩坑

10个月前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!