|
|
@@ -0,0 +1,257 @@
|
|
|
+package com.yinjie.heating.common.tool;
|
|
|
+
|
|
|
+import com.alibaba.fastjson2.JSON;
|
|
|
+import com.alibaba.fastjson2.JSONArray;
|
|
|
+import com.alibaba.fastjson2.JSONObject;
|
|
|
+import org.apache.commons.codec.binary.Base64;
|
|
|
+import org.apache.commons.codec.binary.Hex;
|
|
|
+import org.apache.commons.codec.digest.DigestUtils;
|
|
|
+import org.apache.commons.lang3.StringUtils;
|
|
|
+
|
|
|
+import javax.crypto.Mac;
|
|
|
+import javax.crypto.spec.SecretKeySpec;
|
|
|
+import java.net.URLEncoder;
|
|
|
+import java.nio.charset.StandardCharsets;
|
|
|
+import java.security.KeyFactory;
|
|
|
+import java.security.PublicKey;
|
|
|
+import java.security.Signature;
|
|
|
+import java.security.spec.X509EncodedKeySpec;
|
|
|
+import java.util.*;
|
|
|
+
|
|
|
+public class PaySignatureUtil {
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 生成sign HMAC-SHA256 或 MD5 签名
|
|
|
+ *
|
|
|
+ * @param map map
|
|
|
+ * @param paternerKey paternerKey
|
|
|
+ * @return sign
|
|
|
+ */
|
|
|
+ public static String generateSign(Map<String, String> map, String paternerKey) {
|
|
|
+ return generateSign(map, null, paternerKey);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 生成sign HMAC-SHA256 或 MD5 签名
|
|
|
+ *
|
|
|
+ * @param map map
|
|
|
+ * @param sign_type HMAC-SHA256 或 MD5
|
|
|
+ * @param paternerKey paternerKey
|
|
|
+ * @return sign
|
|
|
+ */
|
|
|
+ public static String generateSign(Map<String, String> map, String sign_type, String paternerKey) {
|
|
|
+ Map<String, String> tmap = PayMapUtil.order(map);
|
|
|
+ tmap.remove("sign");
|
|
|
+ String str = PayMapUtil.mapJoin(tmap, false, false);
|
|
|
+ //System.out.println(str);
|
|
|
+ if (sign_type == null) {
|
|
|
+ sign_type = tmap.get("sign_type");
|
|
|
+ }
|
|
|
+ tmap.remove("sign_type");
|
|
|
+ if ("HMAC-SHA256".equalsIgnoreCase(sign_type)) {
|
|
|
+ try {
|
|
|
+ Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
|
|
|
+ SecretKeySpec secret_key = new SecretKeySpec(paternerKey.getBytes("UTF-8"), "HmacSHA256");
|
|
|
+ sha256_HMAC.init(secret_key);
|
|
|
+ return Hex.encodeHexString(sha256_HMAC.doFinal((str + "&key=" + paternerKey).getBytes("UTF-8"))).toUpperCase();
|
|
|
+ } catch (Exception e) {
|
|
|
+ e.printStackTrace();
|
|
|
+ }
|
|
|
+ return null;
|
|
|
+ } else if ("RSA_1_256".equalsIgnoreCase(sign_type)) {
|
|
|
+ byte[] signBuf = PayRSAUtil.sign(PayRSAUtil.SignatureSuite.SHA256, str.getBytes(StandardCharsets.UTF_8), paternerKey);
|
|
|
+ return new String(Base64.encodeBase64(signBuf), StandardCharsets.UTF_8);
|
|
|
+ } else if ("MD5X".equalsIgnoreCase(sign_type)) {
|
|
|
+ return DigestUtils.md5Hex(str + "&" + paternerKey).toUpperCase();
|
|
|
+ } else {//default MD5
|
|
|
+// System.out.println(str);
|
|
|
+ return DigestUtils.md5Hex(str + "&key=" + paternerKey).toUpperCase();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 生成事件消息接收签名
|
|
|
+ *
|
|
|
+ * @param token token
|
|
|
+ * @param timestamp timestamp
|
|
|
+ * @param nonce nonce
|
|
|
+ * @return str
|
|
|
+ */
|
|
|
+ public static String generateEventMessageSignature(String token, String timestamp, String nonce) {
|
|
|
+ String[] array = new String[]{token, timestamp, nonce};
|
|
|
+ Arrays.sort(array);
|
|
|
+ String s = String.join("", array);
|
|
|
+ //String s = StringUtils.arrayToDelimitedString(array, "");
|
|
|
+ return DigestUtils.shaHex(s);
|
|
|
+ }
|
|
|
+
|
|
|
+ private static Map<String, String> paraFilter(Map<String, String> sArray) {
|
|
|
+ Map<String, String> result = new HashMap<>(sArray.size());
|
|
|
+ if (sArray == null || sArray.size() <= 0) {
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+ for (String key : sArray.keySet()) {
|
|
|
+ String value = sArray.get(key);
|
|
|
+ if (value == null || "".equals(value) || "sign".equalsIgnoreCase(key)) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ result.put(key, value);
|
|
|
+ }
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static String urlEncode(String str) {
|
|
|
+ try {
|
|
|
+ return URLEncoder.encode(str, StandardCharsets.UTF_8);
|
|
|
+ } catch (Throwable e) {
|
|
|
+ return str;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private static void buildPayParams(StringBuilder sb, Map<String, String> payParams, boolean encoding) {
|
|
|
+ List<String> keys = new ArrayList<>(payParams.keySet());
|
|
|
+ Collections.sort(keys);
|
|
|
+ for (String key : keys) {
|
|
|
+ sb.append(key).append("=");
|
|
|
+ if (encoding) {
|
|
|
+ sb.append(urlEncode(payParams.get(key)));
|
|
|
+ } else {
|
|
|
+ sb.append(payParams.get(key));
|
|
|
+ }
|
|
|
+ sb.append("&");
|
|
|
+ }
|
|
|
+ sb.setLength(sb.length() - 1);
|
|
|
+ }
|
|
|
+
|
|
|
+ public static boolean verifySign(String preStr, String sign, String signType, String platPublicKey) {
|
|
|
+ // 调用这个函数前需要先判断是MD5还是RSA
|
|
|
+ // 商户的验签函数要同时支持MD5和RSA
|
|
|
+ PayRSAUtil.SignatureSuite suite = null;
|
|
|
+
|
|
|
+ if ("RSA_1_1".equals(signType)) {
|
|
|
+ suite = PayRSAUtil.SignatureSuite.SHA1;
|
|
|
+ } else if ("RSA_1_256".equals(signType)) {
|
|
|
+ suite = PayRSAUtil.SignatureSuite.SHA256;
|
|
|
+ } else {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ return PayRSAUtil.verifySign(suite,
|
|
|
+ preStr.getBytes(StandardCharsets.UTF_8),
|
|
|
+ Base64.decodeBase64(sign.getBytes(StandardCharsets.UTF_8)),
|
|
|
+ platPublicKey);
|
|
|
+ } catch (Exception e) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public static boolean verifyHuiFu(String data, String publicKeyBase64, String sign) {
|
|
|
+ // Base64 --> Key
|
|
|
+ try {
|
|
|
+ byte[] bytes = java.util.Base64.getDecoder().decode(publicKeyBase64);
|
|
|
+ X509EncodedKeySpec keySpec = new X509EncodedKeySpec(bytes);
|
|
|
+ KeyFactory keyFactory;
|
|
|
+ keyFactory = KeyFactory.getInstance("RSA");
|
|
|
+ PublicKey publicKey = keyFactory.generatePublic(keySpec);
|
|
|
+ // verify
|
|
|
+ Signature signature = Signature.getInstance("SHA256WithRSA");
|
|
|
+ signature.initVerify(publicKey);
|
|
|
+ signature.update(data.getBytes(StandardCharsets.UTF_8));
|
|
|
+ return signature.verify(java.util.Base64.getDecoder().decode(sign));
|
|
|
+ } catch (Exception e) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ public static boolean verifySign(String sign, String signType, Map<String, String> resultMap,
|
|
|
+ String platPublicKey,
|
|
|
+ String md5Key) {
|
|
|
+ if ("RSA_1_256".equals(signType)) {
|
|
|
+ Map<String, String> Reparams = paraFilter(resultMap);
|
|
|
+ StringBuilder Rebuf = new StringBuilder((Reparams.size() + 1) * 10);
|
|
|
+ buildPayParams(Rebuf, Reparams, false);
|
|
|
+ String RepreStr = Rebuf.toString();
|
|
|
+ return verifySign(RepreStr, sign, "RSA_1_256", platPublicKey);
|
|
|
+ } else if ("MD5".equals(signType)) {
|
|
|
+ String newSign = generateSign(resultMap, "MD5", md5Key);
|
|
|
+ return StringUtils.equals(newSign, sign);
|
|
|
+ } else if ("MD5X".equals(signType)) {
|
|
|
+ String newSign = generateSign(resultMap, "MD5X", md5Key);
|
|
|
+ return StringUtils.equals(newSign, sign);
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 对json string进行排序, 排序规则如下:
|
|
|
+ * (1). 如果是对象, 则对对象中的所有的基本数据类型的属性按名称的ASCII升序排序;
|
|
|
+ * (2). 如果是数组, 数组里面的item不进行排序;
|
|
|
+ * (3). 若数组里面包含有对象,请参照规则1.
|
|
|
+ *
|
|
|
+ * @param sourceJson json字符串;
|
|
|
+ * @param maxLayer json允许的最大嵌套层数;
|
|
|
+ * @return 排序好的json字符串.
|
|
|
+ */
|
|
|
+ @SuppressWarnings("rawtypes")
|
|
|
+ public static String sort4JsonString(String sourceJson, int maxLayer) throws Exception {
|
|
|
+ if (StringUtils.isBlank(sourceJson)) {
|
|
|
+ return "";
|
|
|
+ }
|
|
|
+
|
|
|
+ Map m = JSONObject.parseObject(sourceJson, TreeMap.class);
|
|
|
+
|
|
|
+ if (maxLayer > 0) {
|
|
|
+ //对array中的元素顺序进行单独处理;
|
|
|
+ for (Iterator it = m.entrySet().iterator(); it.hasNext(); ) {
|
|
|
+ Map.Entry entry = (Map.Entry) it.next();
|
|
|
+
|
|
|
+ int layer = 0;
|
|
|
+ if (entry.getValue() instanceof JSONArray) {
|
|
|
+ JSONArray array = (JSONArray) entry.getValue();
|
|
|
+
|
|
|
+ sortJsonArray(array, ++layer, maxLayer);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return JSON.toJSONString(m);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 对json Array中的对象进行排序, 基础数据类型不排序.
|
|
|
+ * <p>
|
|
|
+ * 如果有嵌套, 则递归进行排序.
|
|
|
+ *
|
|
|
+ * @param array JSONArray实例.
|
|
|
+ * @param layer json的当前处理的嵌套层数
|
|
|
+ * @param maxLayer json允许最大嵌套层数.
|
|
|
+ */
|
|
|
+ @SuppressWarnings("rawtypes")
|
|
|
+ private static void sortJsonArray(JSONArray array, int layer, int maxLayer) throws Exception {
|
|
|
+ if (layer >= maxLayer) {
|
|
|
+ throw new Exception(String.format("json嵌套层数不超过 %d 层.", maxLayer));
|
|
|
+ }
|
|
|
+
|
|
|
+ for (int i = 0; i < array.size(); i++) {
|
|
|
+ //如果数组中嵌套数组, 则递归排序;
|
|
|
+ if (array.get(i) instanceof JSONArray) {
|
|
|
+ sortJsonArray((JSONArray) array.get(i), ++layer, maxLayer);
|
|
|
+ } else if (!(array.get(i) instanceof Comparable)) {
|
|
|
+ //对数组中的对象进行排序;
|
|
|
+ Map map = JSON.parseObject(array.get(i).toString(), TreeMap.class);
|
|
|
+ array.set(i, map);
|
|
|
+
|
|
|
+ //如果对象中嵌套有数组, 则递归处理;
|
|
|
+ for (Iterator it = map.entrySet().iterator(); it.hasNext(); ) {
|
|
|
+ Map.Entry entry = (Map.Entry) it.next();
|
|
|
+
|
|
|
+ if (entry.getValue() instanceof JSONArray) {
|
|
|
+ sortJsonArray((JSONArray) entry.getValue(), ++layer, maxLayer);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+}
|