电商收付通系列②,获取微信支付平台证书

举报
悟空码字 发表于 2023/05/31 21:09:33 2023/05/31
【摘要】 微信支付平台证书是指由微信支付负责申请的,包含微信支付平台标识、公钥信息的证书。商户可以使用平台证书中的公钥进行验签。注意,这里的证书区别于商户API证书,商户API证书是直接从商户后台下载查看的,而微信支付平台证书是通过电商收付通的证书接口获取的。

大家好,我是小悟

1、介绍

微信支付平台证书是指由微信支付负责申请的,包含微信支付平台标识、公钥信息的证书。商户可以使用平台证书中的公钥进行验签。注意,这里的证书区别于商户API证书,商户API证书是直接从商户后台下载查看的,而微信支付平台证书是通过电商收付通的证书接口获取的。

2、作用

这个证书有什么用?我们需要获取到这个证书相关的序列号和公钥,后续请求一系列接口时需要将微信支付平台证书序列号放在请求头里,而公钥是为了验证应答或回调的签名,以确保应答或回调是由微信支付发送。

3、获取微信支付平台证书

注意:不同的商户,对应的微信支付平台证书是不一样的,平台证书会周期性更换。建议商户定时通过API下载新的证书,不要依赖人工更换证书。微信支付的平台证书序列号位于HTTP头Wechatpay-Serial。验证签名前,请商户先检查序列号是否跟商户当前所持有的微信支付平台证书的序列号一致。如果不一致,请重新获取证书。否则,签名的私钥和证书不匹配,将无法成功验证签名。双手奉上获取微信支付平台证书。

/**
 * @description
 */
public class Certificate {

private static final Logger logger = LoggerFactory.getLogger(Certificate.class);
/**
     * 获取微信支付平台证书
     * @return
     */
public static List<X509Certificate> getCertByAPI(String merchantId, int timeout, String serialNo, String mchPrivateKeyPath,String wechatPubKeyPath,String APIv3Key,String savePath) {
    String result = "";
    //创建http请求
    HttpGet httpGet = new HttpGet("https://api.mch.weixin.qq.com
/v3/certificates");
    httpGet.addHeader("Content-Type", "application/json");
    httpGet.addHeader("Accept", "application/json");

    String authorization = SignUtils.authorization("GET", "/v3/certificates", merchantId, serialNo, "", mchPrivateKeyPath);

    //设置认证信息
    httpGet.setHeader("Authorization", authorization);

    //设置请求器配置:如超时限制等
    RequestConfig config = RequestConfig.custom().setSocketTimeout(timeout * 1000).setConnectTimeout(timeout * 1000).build();
    httpGet.setConfig(config);
    List<X509Certificate> x509Certs = new ArrayList<X509Certificate>();
    try {
        CloseableHttpClient httpClient = HttpClients.createDefault();
        CloseableHttpResponse response = httpClient.execute(httpGet);
        int statusCode = response.getStatusLine().getStatusCode();
        HttpEntity httpEntity = response.getEntity();
        result = EntityUtils.toString(httpEntity, "UTF-8");
        if(statusCode == 200){
            logger.info("下载平台证书返回结果:"+result);
            Header[] timestampHeader = response.getHeaders("Wechatpay-Timestamp");
                Header[] nonceHeader = response.getHeaders("Wechatpay-Nonce");
                Header[] signatureHeader = response.getHeaders("Wechatpay-Signature");
                if (timestampHeader != null && timestampHeader.length > 0 &&
                        nonceHeader != null && nonceHeader.length > 0 &&
                        signatureHeader != null && signatureHeader.length > 0) {
                    // 验证微信支付返回签名
                    String wTimestamp = timestampHeader[0].getValue();
                    String wNonce = nonceHeader[0].getValue();
                    String wSign = signatureHeader[0].getValue();

                    logger.info("wTimestamp:{},wNonce:{},wSign:{}", wTimestamp, wNonce, wSign);
                    // 拼装待签名串
                    StringBuffer ss = new StringBuffer();
                    ss.append(wTimestamp).append("\n");
                    ss.append(wNonce).append("\n");
                    ss.append(result).append("\n");
                    // 验证签名
                    if (SignUtils.v3VerifyRSA(ss.toString(), Base64.decodeBase64(wSign.getBytes()), wechatPubKeyPath)) {
                        List<CertificateItem> certList = new ArrayList<CertificateItem>();
                        JSONObject json = JSONObject.parseObject(result);
                        logger.info("查询结果json字符串转证书List:" + json.get("data"));
                        JSONArray jsonArray = (JSONArray) json.get("data");
                        for (int i = 0; i < jsonArray.size(); i++) {
                            CertificateItem certificateItem = new CertificateItem();
                            EncryptedCertificateItem encryptCertificate = new EncryptedCertificateItem();
                            JSONObject bo = JSONObject.parseObject(jsonArray.get(i).toString());
                            certificateItem.setSerial_no(bo.get("serial_no").toString());
                            certificateItem.setEffective_time(bo.get("effective_time").toString());
                            certificateItem.setExpire_time(bo.get("expire_time").toString());
                            JSONObject encryptBo = JSONObject.parseObject(bo.get("encrypt_certificate").toString());
                            encryptCertificate.setAlgorithm(encryptBo.get("algorithm").toString());
                            encryptCertificate.setNonce(encryptBo.get("nonce").toString());
                            encryptCertificate.setAssociated_data(encryptBo.get("associated_data").toString());
                            encryptCertificate.setCiphertext(encryptBo.get("ciphertext").toString());
                            certificateItem.setEncrypt_certificate(encryptCertificate);
                            certList.add(certificateItem);
                        }
                        logger.info("证书List:" + certList);

                        List<PlainCertificateItem> plainList = decrypt(certList, APIv3Key);
                        if (CollectionUtils.isNotEmpty(plainList)) {
                            logger.info("平台证书开始保存");
                            x509Certs = saveCertificate(plainList, savePath);
                        }
                    }
                }
        }
          response.close();
          httpClient.close(); //throw
          return x509Certs;
      } catch (Exception e) {
          e.printStackTrace();
          logger.error("下载平台证书返回结果:"+e);
      }
      return x509Certs;
  }

//证书保存
private static List<X509Certificate> saveCertificate(List<PlainCertificateItem> cert,String savePath) throws IOException {
    File file = new File(savePath);
    deleteFile(file);
    List<X509Certificate> x509Certs = new ArrayList<X509Certificate>();
    file.mkdirs();
    for (PlainCertificateItem item : cert) {
        ByteArrayInputStream inputStream = new ByteArrayInputStream(item.getPlainCertificate().getBytes(StandardCharsets.UTF_8));
        X509Certificate x509Cert = CertificateUtils.getCertificate(inputStream);
        x509Certs.add(x509Cert);
        String outputAbsoluteFilename = file.getAbsolutePath() + File.separator + "wechat_pay_platform_" + item.getSerialNo() + ".pem";
        try (BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(outputAbsoluteFilename), StandardCharsets.UTF_8))) {
            writer.write(item.getPlainCertificate());
        }
        logger.info("输出证书文件目录:"+outputAbsoluteFilename);
    }
    return x509Certs;
}

private static List<PlainCertificateItem> decrypt(List<CertificateItem> certList,String APIv3Key,CloseableHttpResponse response) throws GeneralSecurityException, IOException{
    List<PlainCertificateItem> plainCertificateList = new ArrayList<PlainCertificateItem>();
    AesUtils aesUtil = new AesUtils(APIv3Key.getBytes(StandardCharsets.UTF_8));
    for(CertificateItem item:certList){
        PlainCertificateItem bo = new PlainCertificateItem();
        bo.setSerialNo(item.getSerial_no());
        bo.setEffectiveTime(item.getEffective_time());
        bo.setExpireTime(item.getExpire_time());
        logger.info("平台证书密文解密");
        bo.setPlainCertificate(aesUtil.decryptToString(item.getEncrypt_certificate().getAssociated_data().getBytes(StandardCharsets.UTF_8),
                item.getEncrypt_certificate().getNonce().getBytes(StandardCharsets.UTF_8), item.getEncrypt_certificate().getCiphertext()));
        logger.info("平台证书公钥明文:"+bo.getPlainCertificate());
        plainCertificateList.add(bo);
    }
    return plainCertificateList;
}

public static void deleteFile(File file){
    //判断文件不为null或文件目录存在
    if (file == null || !file.exists()){
        logger.error("文件删除失败,请检查文件路径是否正确");
        return;
    }
    //取得这个目录下的所有子文件对象
    File[] files = file.listFiles();
    //遍历该目录下的文件对象
    for (File f: files){
        //打印文件名
        String name = f.getName();
        logger.info(name);
        //判断子目录是否存在子目录,如果是文件则删除
        if (f.isDirectory()){
            deleteFile(f);
        }else {
            f.delete();
        }
    }
    //删除空文件夹  for循环已经把上一层节点的目录清空。
    file.delete();
}
}

4、结果

1、接口返回的内容是json串

{
  "data": [{
    "serial_no": "5157F09EFDC096DE15EBE81A47057A7232F1B8E1",
      "effective_time ": "2018-06-08T10:34:56+08:00",
      "expire_time ": "2018-12-08T10:34:56+08:00",
      "encrypt_certificate": {
      "algorithm": "AEAD_AES_256_GCM",
      "nonce": "61f9c719728a",
      "associated_data": "certificate",
      "ciphertext": "sRvt… "
      }
  }]
}

2、通过接口下载的微信支付平台证书

image.png


您的一键三连,是我更新的最大动力,谢谢

山水有相逢,来日皆可期,谢谢阅读,我们再会

我手中的金箍棒,上能通天,下能探海

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。