WXPayUtil.java 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307
  1. package com.fdkankan.pay.wx.sdk;
  2. import com.google.zxing.BarcodeFormat;
  3. import com.google.zxing.EncodeHintType;
  4. import com.google.zxing.MultiFormatWriter;
  5. import com.google.zxing.WriterException;
  6. import com.google.zxing.common.BitMatrix;
  7. import org.w3c.dom.Node;
  8. import org.w3c.dom.NodeList;
  9. import javax.crypto.Mac;
  10. import javax.crypto.spec.SecretKeySpec;
  11. import javax.xml.XMLConstants;
  12. import javax.xml.parsers.DocumentBuilder;
  13. import javax.xml.parsers.DocumentBuilderFactory;
  14. import javax.xml.transform.OutputKeys;
  15. import javax.xml.transform.Transformer;
  16. import javax.xml.transform.TransformerFactory;
  17. import javax.xml.transform.dom.DOMSource;
  18. import javax.xml.transform.stream.StreamResult;
  19. import java.awt.image.BufferedImage;
  20. import java.io.ByteArrayInputStream;
  21. import java.io.InputStream;
  22. import java.io.StringWriter;
  23. import java.security.MessageDigest;
  24. import java.util.*;
  25. public class WXPayUtil {
  26. /**
  27. * XML格式字符串转换为Map
  28. *
  29. * @param strXML XML字符串
  30. * @return XML数据转换后的Map
  31. * @throws Exception
  32. */
  33. public static Map<String, String> xmlToMap(String strXML) throws Exception {
  34. Map<String, String> data = new HashMap<String, String>();
  35. DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
  36. // 禁用XML外部实体注入
  37. /**
  38. * XXE
  39. * XML 外部实体注入漏洞(XML External Entity Injection,简称 XXE),
  40. * 是一种容易被忽视,但危害巨大的漏洞。它可以利用 XML 外部实体加载注入,
  41. * 执行不可预控的代码,可导致读取任意文件、执行系统命令、探测内网端口、攻击内网网站等危害。
  42. */
  43. /**
  44. * 原理:
  45. * 通常,我们在使用微信支付时,商家会有一个通知的 URL 来接收异步支付结果。
  46. * 然而,微信在 JAVA 版本的 SDK 存在一个 XXE 漏洞来处理这个结果。
  47. * 由此攻击者可以向通知的 URL 中构建恶意的回调数据,以便根据需要窃取商家服务器上的任意信息。
  48. * 一旦攻击者获得商家的关键安全密钥(md5-key 和merchant-Id),
  49. * 那么他们可以通过发送伪造的信息来欺骗商家而无需付费购买任意商品。
  50. */
  51. documentBuilderFactory.setExpandEntityReferences(false);
  52. documentBuilderFactory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
  53. DocumentBuilder documentBuilder= documentBuilderFactory.newDocumentBuilder();
  54. InputStream stream = new ByteArrayInputStream(strXML.getBytes("UTF-8"));
  55. org.w3c.dom.Document doc = documentBuilder.parse(stream);
  56. doc.getDocumentElement().normalize();
  57. NodeList nodeList = doc.getDocumentElement().getChildNodes();
  58. for (int idx=0; idx<nodeList.getLength(); ++idx) {
  59. Node node = nodeList.item(idx);
  60. if (node.getNodeType() == Node.ELEMENT_NODE) {
  61. org.w3c.dom.Element element = (org.w3c.dom.Element) node;
  62. data.put(element.getNodeName(), element.getTextContent());
  63. }
  64. }
  65. try {
  66. stream.close();
  67. }
  68. catch (Exception ex) {
  69. }
  70. return data;
  71. }
  72. /**
  73. * 将Map转换为XML格式的字符串
  74. *
  75. * @param data Map类型数据
  76. * @return XML格式的字符串
  77. * @throws Exception
  78. */
  79. public static String mapToXml(Map<String, String> data) throws Exception {
  80. DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
  81. DocumentBuilder documentBuilder= documentBuilderFactory.newDocumentBuilder();
  82. org.w3c.dom.Document document = documentBuilder.newDocument();
  83. org.w3c.dom.Element root = document.createElement("xml");
  84. document.appendChild(root);
  85. for (String key: data.keySet()) {
  86. String value = data.get(key);
  87. if (value == null) {
  88. value = "";
  89. }
  90. value = value.trim();
  91. org.w3c.dom.Element filed = document.createElement(key);
  92. filed.appendChild(document.createTextNode(value));
  93. root.appendChild(filed);
  94. }
  95. TransformerFactory tf = TransformerFactory.newInstance();
  96. Transformer transformer = tf.newTransformer();
  97. DOMSource source = new DOMSource(document);
  98. transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
  99. transformer.setOutputProperty(OutputKeys.INDENT, "yes");
  100. StringWriter writer = new StringWriter();
  101. StreamResult result = new StreamResult(writer);
  102. transformer.transform(source, result);
  103. String output = writer.getBuffer().toString(); //.replaceAll("\n|\r", "");
  104. try {
  105. writer.close();
  106. }
  107. catch (Exception ex) {
  108. }
  109. return output;
  110. }
  111. /**
  112. * 生成带有 sign 的 XML 格式字符串
  113. *
  114. * @param data Map类型数据
  115. * @param key API密钥
  116. * @return 含有sign字段的XML
  117. */
  118. public static String generateSignedXml(final Map<String, String> data, String key) throws Exception {
  119. return generateSignedXml(data, key, WXPayConstants.SignType.MD5);
  120. }
  121. /**
  122. * 生成带有 sign 的 XML 格式字符串
  123. *
  124. * @param data Map类型数据
  125. * @param key API密钥
  126. * @param signType 签名类型
  127. * @return 含有sign字段的XML
  128. */
  129. public static String generateSignedXml(final Map<String, String> data, String key, WXPayConstants.SignType signType) throws Exception {
  130. String sign = generateSignature(data, key, signType);
  131. data.put(WXPayConstants.FIELD_SIGN, sign);
  132. return mapToXml(data);
  133. }
  134. /**
  135. * 判断签名是否正确
  136. *
  137. * @param xmlStr XML格式数据
  138. * @param key API密钥
  139. * @return 签名是否正确
  140. * @throws Exception
  141. */
  142. public static boolean isSignatureValid(String xmlStr, String key) throws Exception {
  143. Map<String, String> data = xmlToMap(xmlStr);
  144. if (!data.containsKey(WXPayConstants.FIELD_SIGN) ) {
  145. return false;
  146. }
  147. String sign = data.get(WXPayConstants.FIELD_SIGN);
  148. return generateSignature(data, key).equals(sign);
  149. }
  150. /**
  151. * 判断签名是否正确,必须包含sign字段,否则返回false。使用MD5签名。
  152. *
  153. * @param data Map类型数据
  154. * @param key API密钥
  155. * @return 签名是否正确
  156. * @throws Exception
  157. */
  158. public static boolean isSignatureValid(Map<String, String> data, String key) throws Exception {
  159. return isSignatureValid(data, key, WXPayConstants.SignType.MD5);
  160. }
  161. /**
  162. * 判断签名是否正确,必须包含sign字段,否则返回false。
  163. *
  164. * @param data Map类型数据
  165. * @param key API密钥
  166. * @param signType 签名方式
  167. * @return 签名是否正确
  168. * @throws Exception
  169. */
  170. public static boolean isSignatureValid(Map<String, String> data, String key, WXPayConstants.SignType signType) throws Exception {
  171. if (!data.containsKey(WXPayConstants.FIELD_SIGN) ) {
  172. return false;
  173. }
  174. String sign = data.get(WXPayConstants.FIELD_SIGN);
  175. return generateSignature(data, key, signType).equals(sign);
  176. }
  177. /**
  178. * 生成签名
  179. *
  180. * @param data 待签名数据
  181. * @param key API密钥
  182. * @return 签名
  183. */
  184. public static String generateSignature(final Map<String, String> data, String key) throws Exception {
  185. return generateSignature(data, key, WXPayConstants.SignType.MD5);
  186. }
  187. /**
  188. * 生成签名. 注意,若含有sign_type字段,必须和signType参数保持一致。
  189. *
  190. * @param data 待签名数据
  191. * @param key API密钥
  192. * @param signType 签名方式
  193. * @return 签名
  194. */
  195. public static String generateSignature(final Map<String, String> data, String key, WXPayConstants.SignType signType) throws Exception {
  196. Set<String> keySet = data.keySet();
  197. String[] keyArray = keySet.toArray(new String[keySet.size()]);
  198. Arrays.sort(keyArray);
  199. StringBuilder sb = new StringBuilder();
  200. for (String k : keyArray) {
  201. if (k.equals(WXPayConstants.FIELD_SIGN)) {
  202. continue;
  203. }
  204. if (data.get(k).trim().length() > 0) // 参数值为空,则不参与签名
  205. sb.append(k).append("=").append(data.get(k).trim()).append("&");
  206. }
  207. sb.append("key=").append(key);
  208. if (WXPayConstants.SignType.MD5.equals(signType)) {
  209. return MD5(sb.toString()).toUpperCase();
  210. }
  211. else if (WXPayConstants.SignType.HMACSHA256.equals(signType)) {
  212. return HMACSHA256(sb.toString(), key);
  213. }
  214. else {
  215. throw new Exception(String.format("Invalid sign_type: %s", signType));
  216. }
  217. }
  218. /**
  219. * 获取随机字符串 Nonce Str
  220. *
  221. * @return String 随机字符串
  222. */
  223. public static String generateNonceStr() {
  224. return UUID.randomUUID().toString().replaceAll("-", "").substring(0, 32);
  225. }
  226. /**
  227. * 生成 MD5
  228. *
  229. * @param data 待处理数据
  230. * @return MD5结果
  231. */
  232. public static String MD5(String data) throws Exception {
  233. MessageDigest md = MessageDigest.getInstance("MD5");
  234. byte[] array = md.digest(data.getBytes("UTF-8"));
  235. StringBuilder sb = new StringBuilder();
  236. for (byte item : array) {
  237. sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3));
  238. }
  239. return sb.toString().toUpperCase();
  240. }
  241. /**
  242. * 生成 HMACSHA256
  243. * @param data 待处理数据
  244. * @param key 密钥
  245. * @return 加密结果
  246. * @throws Exception
  247. */
  248. public static String HMACSHA256(String data, String key) throws Exception {
  249. Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
  250. SecretKeySpec secret_key = new SecretKeySpec(key.getBytes("UTF-8"), "HmacSHA256");
  251. sha256_HMAC.init(secret_key);
  252. byte[] array = sha256_HMAC.doFinal(data.getBytes("UTF-8"));
  253. StringBuilder sb = new StringBuilder();
  254. for (byte item : array) {
  255. sb.append(Integer.toHexString((item & 0xFF) | 0x100).substring(1, 3));
  256. }
  257. return sb.toString().toUpperCase();
  258. }
  259. /**
  260. * 根据url生成二位图片对象
  261. *
  262. * @param codeUrl
  263. * @return
  264. * @throws WriterException
  265. */
  266. public static BufferedImage getQRCodeImge(String codeUrl) throws WriterException {
  267. int width = 300;
  268. //根据url生成二维码
  269. MultiFormatWriter multiFormatWriter = new MultiFormatWriter();
  270. // 设置二维码参数
  271. Map<EncodeHintType, Object> hints = new HashMap<EncodeHintType, Object>();
  272. hints.put(EncodeHintType.CHARACTER_SET, "UTF-8");
  273. BitMatrix bitMatrix = multiFormatWriter.encode(codeUrl, BarcodeFormat.QR_CODE, width, width, hints);
  274. BufferedImage image = new BufferedImage(width, width, 1);
  275. for(int x = 0; x < width; ++x) {
  276. for(int y = 0; y < width; ++y) {
  277. image.setRGB(x, y, bitMatrix.get(x, y) ? -16777216 : -1);
  278. }
  279. }
  280. return image;
  281. }
  282. }