微信加解密流程,证书作用讲解,官方SDK使用教程

新闻资讯 老翟笔记小编 2024-03-10 23:15:58 37 0

老翟笔记今日分享的是:微信加解密流程,证书作用讲解,官方SDK使用教程

微信支付接口常用参数及证书区分 1. 私钥和证书 1.1 商户API证书 1.1.1 功能介绍 API证书,是指由商户申请的,用来证实商户身份的证书。API证书由证书授权机构Certificate Authority(简称CA)颁发。证书中包含商户的商户号、公司名称、公钥等信息。 1.1.2 作用 签名生成 用来对请求url和body啥的签名,就是加密,微信收到请求,用证书公钥解密 商户申请商户API证书时,会生成商户私钥,并保存在本地证书文件夹的文件apiclient_key.pem 中 加密用的就是,apiclient_key.pem文件,生成证书文件时,另外两个没用到 使用商户私钥对待签名串进行SHA256 with RSA签名,并对签名结果进行Base64编码得到签名值 1.2 微信支付平台证书 1.2.1 功能介绍 微信支付平台证书是指由微信支付 负责申请的,包含微信支付平台标识、公钥信息的证书。商户可以使用平台证书中的公钥进行验签。 1.2.2 作用 如果验证商户的请求签名正确,微信支付会在应答的HTTP头部中包括应答签名,这时候就需要微信支付平台证书(给的是公钥)进行验签,保证是微信端的 回调信息和所有请求回来的信息都需要验签就需要,微信支付平台证书,他们那边用这个私钥对报文可能是body啥的加签,我们用公钥验签,保证安全 敏感信息加解密:上传某些重要信息时也需要用到这个微信支付平台证书公钥加密 1.2.3获取 获取平台证书 需要请求接口获取 1.2.4时效性 由于证书存在有效期的限制,微信支付会不定期地更换平台证书以确保交易安全。 估计12小时有效,所以要定期请求更新 1.3 apiv3密钥 为了保证安全性,微信支付在 回调通知和平台证书下载接口中,对关键信息进行了AES-256-GCM加密。API v3密钥是加密时使用的对称密钥。 回调的信息用AES解密(密钥为APIV3值),得到的信息就是回调的明文内容 1.4 常见问题 第一次下载证书 对于微信支付平台的应答,需要使用平台证书来进行验签;但平台证书只能通过 获取平台证书接口 下载,所以当第一次去获取证书时,会出现个“死循环”。 为解决这个“死循环”,可以临时跳过验签,来获得证书。也就是说可以不提供微信支付证书参数(-c 参数)来下载,在下载得到证书后,工具会使用证书对报文的签名进行验证,如果通过则说明证书正确。 如何保证证书正确 工具已经从以下方面去保证了: HTTPS:证书下载请求使用了 HTTPS AES 加密:微信支付对证书信息进行了 AES-256-GCM 加密,所以工具得到应答后,会使用对称密钥来解密证书(这里需要用户传入对称密钥,出于对对称密钥安全的考虑,后续版本将可直接保存未解密的证书,由用户进行解密) 报文验签:微信支付会在应答的 HTTP 头包含签名,工具会通过解密得到的证书,来验证报文的签名,以此确认证书正确 1.5 官方SDK使用 wechatpay-apache-httpclient 实现了请求签名的生成和应答签名的验证,下载证书做了封装,没其他功能 httpClient记得设置超时时间,不然是默认的无超时,到时候接口爆炸就GG 在封装一个通用的工具类,获取HttpClient,实例化一次就够了,但是SDK并没有提供是否已经实例化过,所以只能取巧,从证书管理器中获取verifier是否异常来判断是否有实例化过 警告:这种写法有bug,当apiv3或者其他实例化HttpClient的参数改变时,需要重启应用才能生效,当然可以搞成配置修改时,重新实例化HttpClient @Component public class WxApiV3Utils { public static final String REDIS_KEY_RCHG_PARTNER_PAY_MRCH_CFG = "rchg_partner_pay_mrch_cfg";private static final Logger logger = LoggerFactory.getLogger(WxApiV3Utils.class);private static final Map<String, CloseableHttpClient> MERCHANT_ID_HTTP_CLIENT_MAP = new ConcurrentHashMap<>();/*** 证书管理器实例*/private final CertificatesManager certificatesManager = CertificatesManager.getInstance();@Autowiredprivate RedisUtils redisUtils;public CloseableHttpClient getHttpClient(String merchantId) throws Exception { // 获取ApiV3配置WxPayMrchCfg wxPayMrchCfg = getApiV3Cfg(merchantId);String merchantSerialNumber = wxPayMrchCfg.getCertSerialNo();String apiV3Key = wxPayMrchCfg.getPartnerKey();String certPrivateKeyPath = wxPayMrchCfg.getCertPrivateKeyPath();// 加载商户私钥PrivateKey merchantPrivateKey = PemUtil.loadPrivateKey(new FileInputStream(certPrivateKeyPath));// 获取验签器Verifier verifier = getVerifier(merchantId, merchantSerialNumber, apiV3Key, merchantPrivateKey);Optional<CloseableHttpClient> optional = getHttpClient(merchantId, merchantSerialNumber, merchantPrivateKey,verifier);if (!optional.isPresent()) { throw new RuntimeException("httpClient为空");}return optional.get();}private WxPayMrchCfg getApiV3Cfg(String merchantId) { String merchantIdCfg = redisUtils.getHashValue(REDIS_KEY_RCHG_PARTNER_PAY_MRCH_CFG, merchantId);if (StrUtil.isBlank(merchantIdCfg)) { throw new RuntimeException("查询不到此微信商户号ApiV3配置:" + merchantId);}WxPayMrchCfg wxPayMrchCfg = JSON.parseObject(merchantIdCfg, WxPayMrchCfg.class);if (!doCheckParam(merchantId, wxPayMrchCfg.getCertSerialNo(), wxPayMrchCfg.getPartnerKey(),wxPayMrchCfg.getCertPrivateKeyPath())) { throw new RuntimeException("初始化 CloseableHttpClient 失败,缺少必要参数");}return wxPayMrchCfg;}private Verifier getVerifier(String merchantId, String merchantSerialNumber, String apiV3Key,PrivateKey merchantPrivateKey)throws IOException, GeneralSecurityException, HttpCodeException, NotFoundException { Verifier verifier;try { // 从证书管理器中获取verifierverifier = certificatesManager.getVerifier(merchantId);} catch (IllegalArgumentException | NotFoundException e) { // 向证书管理器增加需要自动更新平台证书的商户信息certificatesManager.putMerchant(merchantId,new WechatPay2Credentials(merchantId,new PrivateKeySigner(merchantSerialNumber, merchantPrivateKey)),apiV3Key.getBytes(StandardCharsets.UTF_8));// 从证书管理器中获取verifierverifier = certificatesManager.getVerifier(merchantId);} catch (Exception e) { throw new RuntimeException("获取verifier失败");}return verifier;}private Optional<CloseableHttpClient> getHttpClient(String merchantId, String merchantSerialNumber,PrivateKey merchantPrivateKey, Verifier verifier) { if (MERCHANT_ID_HTTP_CLIENT_MAP.get(merchantId) == null) { synchronized (WxApiV3Utils.class) { if (MERCHANT_ID_HTTP_CLIENT_MAP.get(merchantId) == null) { // 通过WechatPayHttpClientBuilder构造的HttpClient,会自动的处理签名和验签,并进行证书自动更新CloseableHttpClient newHttpClient = WechatPayHttpClientBuilder.create().withMerchant(merchantId, merchantSerialNumber, merchantPrivateKey).withValidator(new WechatPay2Validator(verifier)).build();MERCHANT_ID_HTTP_CLIENT_MAP.put(merchantId, newHttpClient);}}}return Optional.ofNullable(MERCHANT_ID_HTTP_CLIENT_MAP.get(merchantId));}public BizResponse request(CloseableHttpClient httpClient, HttpPost httpPost, String reqMethodName)throws IOException { RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(5000).setConnectionRequestTimeout(2000).setSocketTimeout(5000).build();httpPost.setConfig(requestConfig);BizResponse bizResponse;int statusCode;// 完成签名并执行请求try (CloseableHttpResponse response = httpClient.execute(httpPost)) { statusCode = response.getStatusLine().getStatusCode();if (statusCode == 200) { // 处理成功bizResponse = BizResponse.success(EntityUtils.toString(response.getEntity()));} else if (statusCode == 204) { // 处理成功,无返回BodybizResponse = BizResponse.success("success");} else { bizResponse = BizResponse.failure(EntityUtils.toString(response.getEntity()));}}logger.info("{} 返回状态码:{},数据:{}", reqMethodName, statusCode, bizResponse.getMsg());return bizResponse;}/*** 获取微信支付平台证书序列号*/public String getPlatformCertificateSerial(String merchantId) throws NotFoundException { return certificatesManager.getVerifier(merchantId).getValidCertificate().getSerialNumber().toString(16);}/*** 敏感信息加密*/public String sensitiveInfoEncrypt(String merchantId, String text) throws Exception { Verifier verifier = CertificatesManager.getInstance().getVerifier(merchantId);X509Certificate certificate = verifier.getValidCertificate();return RsaCryptoUtil.encryptOAEP(text, certificate);}private boolean doCheckParam(String mrchId, String certSerialNo, String apiV3Key, String privateKeyFilePath) { return doCheckValue(mrchId, certSerialNo, apiV3Key, privateKeyFilePath);}private boolean doCheckValue(String... item) { for (String param : item) { if (StrUtil.isBlank(param)) { return false;}}return true;}} 例如请求支付即服务的api可以看下面示例 /*** 支付即服务 https://pay.weixin.qq.com/wiki/doc/apiv3/apis/chapter8_4_1.shtml* */ @Component public class WxSmartGuideApi { private static final Logger logger = LoggerFactory.getLogger(WxSmartGuideApi.class);@Autowiredprivate WxApiV3Utils wxApiV3Utils;/*** 服务人员分配** @param merchantId* 商户id* @param guideId* 服务人员ID* @param outOrderNo* 商户订单号*/public BizResponse assignGuide(String merchantId, String guideId, String outOrderNo) throws Exception { CloseableHttpClient httpClient = wxApiV3Utils.getHttpClient(merchantId);// 请求URLHttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/smartguide/guides/" + guideId + "/assign");// 请求body参数JSONObject body = new JSONObject();body.put("out_trade_no", outOrderNo);logger.info("服务人员分配 url:{},body:{}", httpPost.getURI(), body);initHttpPost(body, httpPost);return wxApiV3Utils.request(httpClient, httpPost, "服务人员分配");}private void initHttpPost(JSONObject body, HttpPost httpPost) { StringEntity entity = new StringEntity(body.toString(), "utf-8");entity.setContentType("application/json");httpPost.setEntity(entity);httpPost.setHeader("Accept", "application/json");}/*** 服务人员注册*/public BizResponse regSmartGuide(String merchantId, String corpId, int storeId, String userId, String mobile,String qrCode, String avatar, String name) throws Exception { CloseableHttpClient httpClient = wxApiV3Utils.getHttpClient(merchantId);// 请求URLHttpPost httpPost = new HttpPost("https://api.mch.weixin.qq.com/v3/smartguide/guides");// 请求body参数JSONObject body = new JSONObject();body.put("corpid", corpId);body.put("store_id", storeId);body.put("userid", userId);body.put("name", wxApiV3Utils.sensitiveInfoEncrypt(merchantId, name));body.put("mobile", wxApiV3Utils.sensitiveInfoEncrypt(merchantId, mobile));body.put("qr_code", qrCode);body.put("avatar", avatar);logger.info("服务人员注册 url:{},body:{}", httpPost.getURI(), body);initHttpPost(body, httpPost);String wechatpaySerial = wxApiV3Utils.getPlatformCertificateSerial(merchantId);httpPost.setHeader("Wechatpay-Serial", wechatpaySerial);return wxApiV3Utils.request(httpClient, httpPost, "服务人员注册");}}

本文结束,感谢您的阅读和支持,希望以上内容能给你带来帮助。本文章来自网络,由老翟笔记小编团队整理发布。

  • 随机文章
  • 热门文章
  • 热评文章

评论区