微信支付V3获取平台证书并解密平台证书详细流程

公司要搞服务号商户卷功能,所以用到了创建商户号接口,有很多数据需要平台证书单独加密,并且图片上传接口返回数据也需要平台证书验签。

   就搞了一下,现在说一下具体细节:

     1:直接点进 敏感信息加密说明

 2 看到微信说加密需要 平台证书

 这里你要搞清楚:商户号证书  和  平台证书不是一个东西,

 商户号证书:是服务商下载的证书

平台证书:微信支付证书

3:现在就去下载微信平台证书,请求接口方式下载

 

 不废话了,直接上获取平台证书代码

   

 //获取 获取平台证书列表  测试成功
   public static void main(String[] args) throws Exception {
        //时间戳
        long timestamp = System.currentTimeMillis() / 1000;
        //随机字符串(用UUID去掉-就行)
        String nonce = IdUtil.fastSimpleUUID().toUpperCase();
        String body =  "";
        //拼接要签名的字符串
        PrivateKey privateKey = PayKit.getPrivateKey("这里填商户平台私钥证书");
        //拼接要签名的字符串
        String orgSignText = "GET\n"
                + "/v3/certificates\n"
                + timestamp + "\n"
                + nonce + "\n"
                + body + "\n";
        // 生成签名
        String sign = RsaKit.encryptByPrivateKey(orgSignText, privateKey);
        //要放在HttpHeader中的auth信息
        // 获取商户证书序列号  这里填写公钥路径 也就是apiclient_cert.pem这个文件的路径
        //证书序列号
        X509Certificate certificate = PayKit.getCertificate(FileUtil.getInputStream("填你的apiclient_cert.pem"));
        String serialNo = certificate.getSerialNumber().toString(16).toUpperCase();
        String auth = "WECHATPAY2-SHA256-RSA2048 "
                + "mchid=\"商户号\",nonce_str=\""
                + nonce + "\",timestamp=\"" + timestamp
                + "\",serial_no=\"" + serialNo + "\",signature=\"" + sign + "\"";
        String url = "https://api.mch.weixin.qq.com/v3/certificates";

        HashMap<String, Object> tmap = new HashMap<String, Object>();
        tmap.put("Authorization",auth);//tmap.put("token","tonken值");
        tmap.put("Accept","application/json");
        tmap.put("User-Agent","https://zh.wikipedia.org/wiki/User_agent");
        String vmsg= httpGet(url,tmap);//获取请求的返回结果

        String rs = HttpClientUtil.sendGetRequest(url, null);
        System.out.println("获取平台证书"+vmsg);
    }

下面是具体用到的工具类:

public class RsaKit {

    /**
     * 加密算法RSA
     */
    private static final String KEY_ALGORITHM = "RSA";

    /**
     * 私钥签名
     *
     * @param data       需要加密的数据
     * @param privateKey 私钥
     * @return 加密后的数据
     * @throws Exception 异常信息
     */
    public static String encryptByPrivateKey(String data, PrivateKey privateKey) throws Exception {
        java.security.Signature signature = java.security.Signature.getInstance("SHA256WithRSA");
        signature.initSign(privateKey);
        signature.update(data.getBytes(StandardCharsets.UTF_8));
        byte[] signed = signature.sign();
        return StrUtil.str(Base64.encode(signed));
    }

    /**
     * 从字符串中加载私钥<br>
     * 加载时使用的是PKCS8EncodedKeySpec(PKCS#8编码的Key指令)。
     *
     * @param privateKeyStr 私钥
     * @return {@link PrivateKey}
     * @throws Exception 异常信息
     */
    public static PrivateKey loadPrivateKey(String privateKeyStr) throws Exception {
        try {
            byte[] buffer = Base64.decode(privateKeyStr);
            PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(buffer);
            KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
            return keyFactory.generatePrivate(keySpec);
        } catch (NoSuchAlgorithmException e) {
            throw new Exception("无此算法");
        } catch (InvalidKeySpecException e) {
            throw new Exception("私钥非法");
        } catch (NullPointerException e) {
            throw new Exception("私钥数据为空");
        }
    }
    /**
     * 公钥验证签名
     *
     * @param data      需要加密的数据
     * @param sign      签名
     * @param publicKey 公钥
     * @return 验证结果
     * @throws Exception 异常信息
     */
    public static boolean checkByPublicKey(String data, String sign, PublicKey publicKey) throws Exception {
        java.security.Signature signature = java.security.Signature.getInstance("SHA256WithRSA");
        signature.initVerify(publicKey);
        signature.update(data.getBytes(StandardCharsets.UTF_8));
        return signature.verify(Base64.decode(sign.getBytes(StandardCharsets.UTF_8)));
    }
}
public class PayKit {

    /**
     * 获取证书
     *
     * @param inputStream 证书文件
     * @return {@link X509Certificate} 获取证书
     */
    public static X509Certificate getCertificate(InputStream inputStream) {
        try {
            CertificateFactory cf = CertificateFactory.getInstance("X509");
            X509Certificate cert = (X509Certificate) cf.generateCertificate(inputStream);
            cert.checkValidity();
            return cert;
        } catch (CertificateExpiredException e) {
            throw new RuntimeException("证书已过期", e);
        } catch (CertificateNotYetValidException e) {
            throw new RuntimeException("证书尚未生效", e);
        } catch (CertificateException e) {
            throw new RuntimeException("无效的证书", e);
        }
    }
    /**
     * 简化的UUID,去掉了横线,使用性能更好的 ThreadLocalRandom 生成UUID
     *
     * @return 简化的 UUID,去掉了横线
     */
    public static String generateStr() {
        return IdUtil.fastSimpleUUID();
    }

    /**
     * 构造签名串
     *
     * @param method    {@link RequestMethod} GET,POST,PUT等
     * @param url       请求接口 /v3/certificates
     * @param timestamp 获取发起请求时的系统当前时间戳
     * @param nonceStr  随机字符串
     * @param body      请求报文主体
     * @return 待签名字符串
     */
    public static String buildSignMessage(String method, String url, String timestamp, String nonceStr, String body) {
        ArrayList<String> arrayList = new ArrayList<>();
        arrayList.add(method);
        arrayList.add(url);
        arrayList.add(String.valueOf(timestamp));
        arrayList.add(nonceStr);
        arrayList.add(body);
        return buildSignMessage(arrayList);
    }
    /**
     * 构造签名串
     *
     * @param signMessage 待签名的参数
     * @return 构造后带待签名串
     */
    public static String buildSignMessage(ArrayList<String> signMessage) {
        if (signMessage == null || signMessage.size() <= 0) {
            return null;
        }
        StringBuilder sbf = new StringBuilder();
        for (String str : signMessage) {
            sbf.append(str).append("\n");
        }
        System.out.println("待签名字符串内容:" + sbf.toString());
        System.out.println("待签名字符串长度:" + sbf.toString().length());
        return sbf.toString();
    }
    /**
     * v3 接口创建签名
     *
     * @param signMessage 待签名的参数
     * @param keyPath     key.pem 证书路径
     * @return 生成 v3 签名
     * @throws Exception 异常信息
     */
    public static String createSign(String signMessage, String keyPath) throws Exception {
        if (StrUtil.isEmpty(signMessage)) {
            return null;
        }
        // 获取商户私钥
        PrivateKey privateKey = PayKit.getPrivateKey(keyPath);
        // 生成签名
        return RsaKit.encryptByPrivateKey(signMessage, privateKey);
    }

    /**
     * 获取商户私钥
     *
     * @param keyPath 商户私钥证书路径
     * @return {@link PrivateKey} 商户私钥
     * @throws Exception 异常信息
     */
    public static PrivateKey getPrivateKey(String keyPath) throws Exception {
        String originalKey = FileUtil.readUtf8String(keyPath);
        String privateKey = originalKey
                .replace("-----BEGIN PRIVATE KEY-----", "")
                .replace("-----END PRIVATE KEY-----", "")
                .replaceAll("\\s+", "");

        return RsaKit.loadPrivateKey(privateKey);
    }

    /**
     * 获取授权认证信息
     *
     * @param mchId     商户号
     * @param serialNo  商户API证书序列号
     * @param nonceStr  请求随机串
     * @param timestamp 时间戳
     * @param signature 签名值
     * @param authType  认证类型,目前为WECHATPAY2-SHA256-RSA2048
     * @return 请求头 Authorization
     */
    public static String getAuthorization(String mchId, String serialNo, String nonceStr, String timestamp, String signature, String authType) {
        Map<String, String> params = new HashMap<>(5);
        params.put("mchid", mchId);
        params.put("serial_no", serialNo);
        params.put("nonce_str", nonceStr);
        params.put("timestamp", timestamp);
        params.put("signature", signature);
        return authType.concat(" ").concat(createLinkString(params, ",", false, true));
    }
    /**
     * @param params 需要排序并参与字符拼接的参数组
     * @param encode 是否进行URLEncoder
     * @return 拼接后字符串
     */
    public static String createLinkString(Map<String, String> params, boolean encode) {
        return createLinkString(params, "&", encode);
    }
    /**
     * @param params  需要排序并参与字符拼接的参数组
     * @param connStr 连接符号
     * @param encode  是否进行URLEncoder
     * @return 拼接后字符串
     */
    public static String createLinkString(Map<String, String> params, String connStr, boolean encode) {
        return createLinkString(params, connStr, encode, false);
    }

    public static String createLinkString(Map<String, String> params, String connStr, boolean encode, boolean quotes) {
        List<String> keys = new ArrayList<>(params.keySet());
        Collections.sort(keys);
        StringBuilder content = new StringBuilder();
        for (int i = 0; i < keys.size(); i++) {
            String key = keys.get(i);
            String value = params.get(key);
            // 拼接时,不包括最后一个&字符
            if (i == keys.size() - 1) {
                if (quotes) {
                    content.append(key).append("=").append('"').append(encode ? urlEncode(value) : value).append('"');
                } else {
                    content.append(key).append("=").append(encode ? urlEncode(value) : value);
                }
            } else {
                if (quotes) {
                    content.append(key).append("=").append('"').append(encode ? urlEncode(value) : value).append('"').append(connStr);
                } else {
                    content.append(key).append("=").append(encode ? urlEncode(value) : value).append(connStr);
                }
            }
        }
        return content.toString();
    }

    /**
     * URL 编码
     *
     * @param src 需要编码的字符串
     * @return 编码后的字符串
     */
    public static String urlEncode(String src) {
        try {
            return URLEncoder.encode(src, CharsetUtil.UTF_8).replace("+", "%20");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 构造签名串
     *
     * @param timestamp 应答时间戳
     * @param nonceStr  应答随机串
     * @param body      应答报文主体
     * @return 应答待签名字符串
     */
    public static String buildSignMessage(String timestamp, String nonceStr, String body) {
        ArrayList<String> arrayList = new ArrayList<>();
        arrayList.add(timestamp);
        arrayList.add(nonceStr);
        arrayList.add(body);
        return buildSignMessage(arrayList);
    }

    /**
     * 公钥加密
     *
     * @param data        待加密数据
     * @param certificate 平台公钥证书
     * @return 加密后的数据
     * @throws Exception 异常信息
     */
    public static String rsaEncryptOAEP(String data, X509Certificate certificate) throws Exception {
        try {
            Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-1AndMGF1Padding");
            cipher.init(Cipher.ENCRYPT_MODE, certificate.getPublicKey());

            byte[] dataByte = data.getBytes(StandardCharsets.UTF_8);
            byte[] cipherData = cipher.doFinal(dataByte);
            return Base64.encode(cipherData);
        } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
            throw new RuntimeException("当前Java环境不支持RSA v1.5/OAEP", e);
        } catch (InvalidKeyException e) {
            throw new IllegalArgumentException("无效的证书", e);
        } catch (IllegalBlockSizeException | BadPaddingException e) {
            throw new IllegalBlockSizeException("加密原串的长度不能超过214字节");
        }
    }

    /**
     * 私钥解密
     *
     * @param cipherText 加密字符
     * @param privateKey 私钥
     * @return 解密后的数据
     * @throws Exception 异常信息
     */
    public static String rsaDecryptOAEP(String cipherText, PrivateKey privateKey) throws Exception {
        try {
            Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-1AndMGF1Padding");
            cipher.init(Cipher.DECRYPT_MODE, privateKey);
            byte[] data = Base64.decode(cipherText);
            return new String(cipher.doFinal(data), StandardCharsets.UTF_8);
        } catch (NoSuchPaddingException | NoSuchAlgorithmException e) {
            throw new RuntimeException("当前Java环境不支持RSA v1.5/OAEP", e);
        } catch (InvalidKeyException e) {
            throw new IllegalArgumentException("无效的私钥", e);
        } catch (BadPaddingException | IllegalBlockSizeException e) {
            throw new BadPaddingException("解密失败");
        }
    }
}
public class HttpClientUtil
 {
   public static final String SunX509 = "SunX509";
   public static final String JKS = "JKS";
   public static final String PKCS12 = "PKCS12";
   public static final String TLS = "TLS";
   
   public static HttpURLConnection getHttpURLConnection(String strUrl)
     throws IOException
   {
     URL url = new URL(strUrl);
     HttpURLConnection httpURLConnection = (HttpURLConnection)url
       .openConnection();
     return httpURLConnection;
   }
   
 
 
   /**
	 * 发送HTTP_GET请求
	 * 
	 * @see 该方法会自动关闭连接,释放资源
	 * @param reqURL
	 *            请求地址(含参数)
	 * @param decodeCharset
	 *            解码字符集,解析响应数据时用之,其为null时默认采用UTF-8解码
	 * @return 远程主机响应正文
	 */
	public static String sendGetRequest(String reqURL, String decodeCharset) {
		long responseLength = 0; // 响应长度
		String responseContent = null; // 响应内容
		HttpClient httpClient = new DefaultHttpClient(); // 创建默认的httpClient实例
		HttpGet httpGet = new HttpGet(reqURL); // 创建org.apache.http.client.methods.HttpGet
		try {
			HttpResponse response = httpClient.execute(httpGet); // 执行GET请求
			HttpEntity entity = response.getEntity(); // 获取响应实体
			if (null != entity) {
				responseLength = entity.getContentLength();
				responseContent = EntityUtils.toString(entity, decodeCharset == null ? "UTF-8" : decodeCharset);
				EntityUtils.consume(entity); // Consume response content
			}
			System.out.println("请求地址: " + httpGet.getURI());
			System.out.println("响应状态: " + response.getStatusLine());
			System.out.println("响应长度: " + responseLength);
			System.out.println("响应内容: " + responseContent);
		} catch (ClientProtocolException e) {
			System.out.println("该异常通常是协议错误导致,比如构造HttpGet对象时传入的协议不对(将'http'写成'htp')或者服务器端返回的内容不符合HTTP协议要求等,堆栈信息如下");
		} catch (ParseException e) {
			System.out.println(e.getMessage());
		} catch (IOException e) {
			System.out.println("该异常通常是网络原因引起的,如HTTP服务器未启动等,堆栈信息如下");
		} finally {
			httpClient.getConnectionManager().shutdown(); // 关闭连接,释放资源
		}
		return responseContent;
	}
 
	/**
	 * 发送HTTP_POST请求
	 * 
	 * @see 该方法为
	 *      <code>sendPostRequest(String,String,boolean,String,String)</code>
	 *      的简化方法
	 * @see 该方法在对请求数据的编码和响应数据的解码时,所采用的字符集均为UTF-8
	 * @see 当<code>isEncoder=true</code>时,其会自动对<code>sendData</code>中的[中文][|][
	 *      ]等特殊字符进行<code>URLEncoder.encode(string,"UTF-8")</code>
	 * @param isEncoder
	 *            用于指明请求数据是否需要UTF-8编码,true为需要
	 */
	public static String sendPostRequest(String reqURL, String sendData, boolean isEncoder) {
		return sendPostRequest(reqURL, sendData, isEncoder, null, null,"application/json");
	}
	
	/**
	 * 发送HTTP_POST请求
	 * 
	 * @see 该方法会自动关闭连接,释放资源
	 * @see 当<code>isEncoder=true</code>时,其会自动对<code>sendData</code>中的[中文][|][
	 *      ]等特殊字符进行<code>URLEncoder.encode(string,encodeCharset)</code>
	 * @param reqURL
	 *            请求地址
	 * @param sendData
	 *            请求参数,若有多个参数则应拼接成param11=value11¶m22=value22¶m33=value33的形式后,
	 *            传入该参数中
	 * @param isEncoder
	 *            请求数据是否需要encodeCharset编码,true为需要
	 * @param encodeCharset
	 *            编码字符集,编码请求数据时用之,其为null时默认采用UTF-8解码
	 * @param decodeCharset
	 *            解码字符集,解析响应数据时用之,其为null时默认采用UTF-8解码
	 * @return 远程主机响应正文
	 */
	public static String sendPostRequest(String reqURL, String sendData, boolean isEncoder, String encodeCharset,
			String decodeCharset,String contentType) {
		String responseContent = null;
		HttpClient httpClient = new DefaultHttpClient();

		HttpPost httpPost = new HttpPost(reqURL);
		// httpPost.setHeader(HTTP.CONTENT_TYPE,
		// "application/x-www-form-urlencoded; charset=UTF-8");
		httpPost.setHeader(HTTP.CONTENT_TYPE, contentType);
		try {
			if (isEncoder) {
				httpPost.setEntity(new StringEntity(sendData, encodeCharset == null ? "UTF-8" : encodeCharset));
			} else {
				httpPost.setEntity(new StringEntity(sendData));
			}
			// 设置请求超时时间
			httpClient.getParams().setParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, 5000);
			HttpResponse response = httpClient.execute(httpPost);
			HttpEntity entity = response.getEntity();
			if (null != entity) {
				responseContent = EntityUtils.toString(entity, decodeCharset == null ? "UTF-8" : decodeCharset);
				EntityUtils.consume(entity);
			}
		} catch (Exception e) {
			System.out.println("与[" + reqURL + "]通信过程中发生异常,堆栈信息如下");
			e.printStackTrace();
		} finally {
			httpClient.getConnectionManager().shutdown();
		}
		return responseContent;
	}
 
 
   public static HttpsURLConnection getHttpsURLConnection(String strUrl)
     throws IOException
   {
     URL url = new URL(strUrl);
     HttpsURLConnection httpsURLConnection = (HttpsURLConnection)url
       .openConnection();
     return httpsURLConnection;
   }
   



   public static String getURL(String strUrl)
   {
     if (strUrl != null) {
       int indexOf = strUrl.indexOf("?");
       if (-1 != indexOf) {
         return strUrl.substring(0, indexOf);
       }
       
       return strUrl;
     }
     
     return strUrl;
   }



   public static String getQueryString(String strUrl)
   {
     if (strUrl != null) {
       int indexOf = strUrl.indexOf("?");
       if (-1 != indexOf) {
         return strUrl.substring(indexOf + 1, strUrl.length());
       }
       
       return "";
     }
     
     return strUrl;
   }
   

 
   public static Map queryString2Map(String queryString)
   {
     if ((queryString == null) || ("".equals(queryString))) {
       return null;
     }
     
     Map m = new HashMap();
     String[] strArray = queryString.split("&");
     for (int index = 0; index < strArray.length; index++) {
       String pair = strArray[index];
       putMapByPair(pair, m);
     }
     
     return m;
   }
   

 
   public static void putMapByPair(String pair, Map m)
   {
     if ((pair == null) || ("".equals(pair))) {
       return;
     }
     
     int indexOf = pair.indexOf("=");
     if (-1 != indexOf) {
       String k = pair.substring(0, indexOf);
       String v = pair.substring(indexOf + 1, pair.length());
       if ((k != null) && (!"".equals(k))) {
         m.put(k, v);
       }
     } else {
       m.put(pair, "");
     }
   }
   

 
 
   public static String bufferedReader2String(BufferedReader reader)
     throws IOException
   {
     StringBuffer buf = new StringBuffer();
     String line = null;
     while ((line = reader.readLine()) != null) {
       buf.append(line);
       buf.append("\r\n");
     }
     
     return buf.toString();
   }
   
 
 

   public static void doOutput(OutputStream out, byte[] data, int len)
     throws IOException
   {
     int dataLen = data.length;
     int off = 0;
     while (off < dataLen) {
       if (len >= dataLen) {
         out.write(data, off, dataLen);
       } else {
         out.write(data, off, len);
       }
       
 
       out.flush();
       
       off += len;
       
       dataLen -= len;
     }
   }
   

 
 
   public static SSLContext getSSLContext(FileInputStream trustFileInputStream, String trustPasswd, FileInputStream keyFileInputStream, String keyPasswd)
     throws NoSuchAlgorithmException, KeyStoreException, CertificateException, IOException, UnrecoverableKeyException, KeyManagementException
   {
     TrustManagerFactory tmf = 
       TrustManagerFactory.getInstance("SunX509");
     KeyStore trustKeyStore = KeyStore.getInstance("JKS");
     trustKeyStore.load(trustFileInputStream, 
       str2CharArray(trustPasswd));
     tmf.init(trustKeyStore);
     
     char[] kp = str2CharArray(keyPasswd);
     KeyManagerFactory kmf = 
       KeyManagerFactory.getInstance("SunX509");
     KeyStore ks = KeyStore.getInstance("PKCS12");
     ks.load(keyFileInputStream, kp);
     kmf.init(ks, kp);
     
     SecureRandom rand = new SecureRandom();
     SSLContext ctx = SSLContext.getInstance("TLS");
     ctx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), rand);
     
     return ctx;
   }
   
 

 
 
 
   public static Certificate getCertificate(File cafile)
     throws CertificateException, IOException
   {
     CertificateFactory cf = CertificateFactory.getInstance("X.509");
     FileInputStream in = new FileInputStream(cafile);
     Certificate cert = cf.generateCertificate(in);
     in.close();
     return cert;
   }
   
 
 
 
 
 
   public static char[] str2CharArray(String str)
   {
     if (str == null) {
       return null;
     }
     return str.toCharArray();
   }
   
 
 
 

 
 
   public static void storeCACert(Certificate cert, String alias, String password, OutputStream out)
     throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException
   {
     KeyStore ks = KeyStore.getInstance("JKS");
     
     ks.load(null, null);
     
     ks.setCertificateEntry(alias, cert);
     
 
     ks.store(out, str2CharArray(password));
   }
   
   public static InputStream String2Inputstream(String str)
   {
     return new ByteArrayInputStream(str.getBytes());
   }
 }

返回结果:

这就获取到了微信平台证书,但是要注意,这只是获取到了平台证书加密密文 还要进行解密,直接用微信支付文档里面的解密代码就可以。

 

然后解析出真正的微信证书。

注意这里解密有个问题:解密出现Illegal key size错误

   解决:微信支付V3支付通知JAVA解密出现Illegal key size错误 | 微信开放社区

可以用微信证书去加/解密指定内容:

 好了,结束,希望对你有所帮助!

评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值