赞
踩
公司要搞服务号商户卷功能,所以用到了创建商户号接口,有很多数据需要平台证书单独加密,并且图片上传接口返回数据也需要平台证书验签。
就搞了一下,现在说一下具体细节:
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错误 | 微信开放社区
可以用微信证书去加/解密指定内容:
好了,结束,希望对你有所帮助!
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。