package com.nova.sankuai.domain.api.yixin; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONObject; import com.nova.sankuai.security.HexUtil; import javax.crypto.Cipher; import javax.crypto.spec.SecretKeySpec; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.security.*; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.*; public class YXSignUtil { public static final String reqSysCode = "WHKJ"; public static final String KEY_ALGORITHM = "RSA"; //本地公钥 public static final String LOCAL_PUBLIC_KEY = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDPlqGyVfCliU1SmhdGKem9+p63rIb78bu3Yj9DWQv1c+fbFLV0OkfeMvyTAJi95PozH+JlDqt3gWWLyCXEb7M6pPNEJmUjGHX4+tCykUbooY55zr8En3DM4MMRvOz5hhkle7tosT4Iejhtuwsah9heWd4AuM84gw8C7LVA7XbOkQIDAQAB"; //本地私钥 public static final String LOCAL_PRIVATE_KEY = "MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAM+WobJV8KWJTVKaF0Yp6b36nreshvvxu7diP0NZC/Vz59sUtXQ6R94y/JMAmL3k+jMf4mUOq3eBZYvIJcRvszqk80QmZSMYdfj60LKRRuihjnnOvwSfcMzgwxG87PmGGSV7u2ixPgh6OG27CxqH2F5Z3gC4zziDDwLstUDtds6RAgMBAAECgYAxZAya6tmz+SQdmC4bcpN7sSqcVv9S6KQaMNUOiBxRTT+IH7hArDE3S/hOXaD55YYmLdrm4oOnjnEDvh5GS7Ffv9U5i+7Dvf4u4rzvucBwj2gIWVVzQgjT2j2LXcBpExWLTEEE37WrJogXWJZk/iKFB6QpxnsFTi9bmFftO37S8QJBAPIIeHftqutrg55bjbM+NxAoa4oMNEnI7Ri3X7pmjWk7Zzjtl6c5nvAL5qbhXO3SdgF/+y7XtH2LUR6iRY2vuj0CQQDbkVA36O/dSQcq2LFf3J+kd1j2Z/j6BxC816Q7CchWvpvvXUm+9p+uiPX8LmCWTeVrkp3qr7mWwBDVyChJK27lAkA7JujSXqqkKL2dKUEDapQohchqj7sDwXB41vA1bTToYBVFK4Qh4Yo/npj7dh6xHPusOCwacatx92eW9g/LpgP9AkEAgW561EqLQ23uPLK6dOEQdpooJjEKUxFhK4EO/gJ5R7FbKNJcS6cEYJW6M+t+4nuO10f5sUPfal9UTUGNhfyFhQJAGNcLGWQvWuC2Si0vI5xKRhLYblb3MrwGKd/Mj6UdURq9qsHdjqvaW9rx0TjZ9uCb3YbCSGFO0nWavkkqJICO1Q=="; /** * 构造合作方请求绿地报文 * * @param bizMap 业务报文集 * @param privateKey 合作方私钥 * @param publicKey 绿地公钥 * @return */ public static String buildRequest(Map bizMap, PrivateKey privateKey, PublicKey publicKey, String funding) throws Exception { // 业务报文 String params = JSON.toJSONString(bizMap); // 时间戳 String timestamp = DateTimeFormatter.ofPattern("yyyyMMddHHmmss").format(LocalDateTime.now()); // 构建签名 SortedMap map = new TreeMap<>(); map.put("params", params); map.put("timestamp", timestamp); // 待签名明文 String plain = toSortedString(map); // 做md5摘要处理 String md5 = MD5.digest(plain, StandardCharsets.UTF_8); // 签名 String sign = sign(md5, privateKey); map.put("sign", sign); // 生成16位AES密钥 String aesKey = generateAesKey(); // 对params进行AES加密, 并替换进map params = encrypt(params, aesKey); map.replace("params", params); // 对AES密钥进行RSA加密, 并将加密后的AES密钥加入map aesKey = encryptRSA(aesKey, publicKey); map.put("key", aesKey); map.put("reqSysCode", reqSysCode); map.put("funding", funding); return JSON.toJSONString(map); } /** * 绿地处理合作方发起的请求 * * @param strReq 合作方请求到达绿地的json字符串请求报文 * @param privateKey 绿地私钥 * @param publicKey 合作方公钥 * @return */ public static Map parseRequest(String strReq, PrivateKey privateKey, PublicKey publicKey) throws Exception { JSONObject json = JSON.parseObject(strReq); String params = json.getString("params"); String aesKey = json.getString("key"); String sign = json.getString("sign"); String timestamp = json.getString("timestamp"); // 对AESkey进行RSA解密 aesKey = decryptRSA(aesKey, privateKey); // 对params进行AES解密 params = decrypt(params, aesKey); // 构建验签明文 TreeMap map = new TreeMap<>(); map.put("params", params); map.put("timestamp", timestamp); String plain = toSortedString(map); // 做md5摘要 String md5 = MD5.digest(plain, StandardCharsets.UTF_8); // 验签 boolean passed = verify(sign, md5, publicKey); if (!passed) { throw new RuntimeException("验签失败"); } return JSON.parseObject(params); } /** * 绿地处理合作方发起的请求 * * @param strReq 合作方请求到达绿地的json字符串请求报文 * @param privateKey 本地私钥 * @param publicKey 绿地公钥 * @return */ public static Map parseLVDiRequest(Map strReq, PrivateKey privateKey, PublicKey publicKey) throws Exception { String params = strReq.get("params"); String aesKey = strReq.get("key"); String signkeyIndex = strReq.get("signkeyIndex"); String appId = strReq.get("appId"); String version = strReq.get("version"); String sign = strReq.get("sign"); String timestamp = strReq.get("timestamp"); // 对AESkey进行RSA解密 aesKey = decryptRSA(aesKey, privateKey); // 对params进行AES解密 params = decrypt(params, aesKey); // 构建验签明文 Map map = new HashMap<>(); map.put("params", params); map.put("timestamp", timestamp); map.put("signkeyIndex", signkeyIndex); map.put("appId", appId); map.put("version", version); // String plain = toSortedString(map); // 做md5摘要 // String md5 = MD5.digest(plain, StandardCharsets.UTF_8); // 验签 // boolean passed = verify(sign, md5, publicKey); // if (!passed) { // throw new RuntimeException("验签失败"); // } return map; } /** * 绿地给合作方的同步响应报文构建 * * @param bizMap 业务报文 * @param privateKey 绿地私钥 * @param publicKey 合作方公钥 * @return */ public static String buildResponse(Map bizMap, PrivateKey privateKey, PublicKey publicKey) throws Exception { JSONObject json = new JSONObject(); TreeMap map = new TreeMap<>(); String params = JSON.toJSONString(bizMap); map.put("params", params); // 处理签名 String plain = toSortedString(map); String md5 = MD5.digest(plain, StandardCharsets.UTF_8); String sign = sign(md5, privateKey); map.put("sign", sign); // 处理加密 String aesKey = generateAesKey(); params = encrypt(params, aesKey); map.replace("params", params); map.put("key", encryptRSA(aesKey, publicKey)); map.put("encrypt", "true"); return JSON.toJSONString(map); } /** * 绿地回调请求合作方报文构建 * * @param bizMap 业务报文 * @param privateKey 绿地私钥 * @param publicKey 合作方公钥 * @return */ public static String buildCallbackRequest(Map bizMap, PrivateKey privateKey, PublicKey publicKey) throws Exception { // 参数 String params = JSON.toJSONString(bizMap); // 签名 TreeMap map = new TreeMap<>(); map.put("params", params); map.put("timestamp", DateTimeFormatter.ofPattern("yyyyMMddHHmmss").format(LocalDateTime.now())); map.put("appId", "");// 固定,待合作方分配 map.put("version", "1.0");// 固定,由具体接口文档定 String plain = toSortedString(map); // MD5 String md5 = MD5.digest(plain, StandardCharsets.UTF_8); // 签名 String sign = sign(md5, privateKey); map.put("sign", sign); // AES密钥 String aesKey = generateAesKey(); // 加密 map.replace("params", encrypt(params, aesKey)); map.put("key", encryptRSA(aesKey, publicKey)); map.put("signType", "");// 具体由接口文档定 map.put("signkeyIndex", "");// 固定,由合作方分配 return JSON.toJSONString(map); } /** * 绿地处理合作方的回调响应 * * @param strResp * @param privateKey * @param publicKey * @return */ public static String parseCallbackResponse(String strResp, PrivateKey privateKey, PublicKey publicKey) throws Exception { JSONObject json = JSON.parseObject(strResp); String sign = (String) json.remove("gsSign"); TreeMap map = new TreeMap<>(); json.forEach((k, v) -> map.put(k, v.toString())); String plain = toSortedString(map); String md5 = MD5.digest(plain, StandardCharsets.UTF_8); boolean verify = verify(sign, md5, publicKey); if (!verify) { throw new RuntimeException("验签失败"); } String params = json.getString("params"); Boolean encrypted = json.getBoolean("encrypt"); if (encrypted != null && encrypted) { String aesKey = json.getString("key"); aesKey = decryptRSA(aesKey, privateKey); params = decrypt(params, aesKey); } return params; } /** * RSA签名 * * @param data * @param privateKey * @return */ public static String sign(String data, PrivateKey privateKey) throws Exception { Signature signature = Signature.getInstance("SHA1WithRSA"); signature.initSign(privateKey); signature.update(data.getBytes(StandardCharsets.UTF_8)); return Base64.getEncoder().encodeToString(signature.sign()); } /** * 验签 * * @param sign * @param md5 * @param publicKey * @return */ public static boolean verify(String sign, String md5, PublicKey publicKey) throws Exception { byte[] signBytes = Base64.getDecoder().decode(sign); Signature signature = Signature.getInstance("SHA1WithRSA"); signature.initVerify(publicKey); signature.update(md5.getBytes(StandardCharsets.UTF_8)); return signature.verify(signBytes); } /** * 将需要加签的数据拼接为'='和'&'拼接的格式 * * @param map * @return */ private static String toSortedString(SortedMap map) { StringBuilder sb = new StringBuilder(); Iterator> it = map.entrySet().iterator(); while (true) { Map.Entry entry = it.next(); sb.append(entry.getKey()).append('=').append(entry.getValue()); if (it.hasNext()) { sb.append('&'); } else { break; } } return sb.toString(); } /** * 生成16位不重复的随机数,含数字+大小写 * 作为AES密钥使用 */ private static String generateAesKey() { StringBuilder uid = new StringBuilder(); //产生16位的强随机数 Random rd = new SecureRandom(); for (int i = 0; i < 16; i++) { int type = rd.nextInt(3); switch (type) { case 0: uid.append(rd.nextInt(10)); break; case 1: uid.append((char) (rd.nextInt(25) + 65)); break; case 2: uid.append((char) (rd.nextInt(25) + 97)); break; default: break; } } return uid.toString(); } /** * AES加密 * * @param content 需要加密的内容 * @param strKey 加密秘钥 * @return 加密后的比特流 */ private static String encrypt(String content, String strKey) throws Exception { SecretKeySpec key = new SecretKeySpec(strKey.getBytes(), "AES"); //NOSONAR Cipher cipher = Cipher.getInstance("AES"); byte[] byteContent = content.getBytes(StandardCharsets.UTF_8); cipher.init(Cipher.ENCRYPT_MODE, key); return byte2hex(cipher.doFinal(byteContent)); } /** * AES解密 * * @param content 待解密内容 * @param strKey 解密密钥 * @return 解密后的 */ private static String decrypt(String content, String strKey) throws Exception { SecretKeySpec key = new SecretKeySpec(strKey.getBytes(), "AES"); Cipher cipher = Cipher.getInstance("AES"); cipher.init(Cipher.DECRYPT_MODE, key); return new String(cipher.doFinal(hex2byte(content))); } /** * RSA加密 * * @param data * @param publicKey * @return */ public static String encryptRSA(String data, PublicKey publicKey) throws Exception { // 对数据加密 Cipher cipher = Cipher.getInstance("RSA"); cipher.init(Cipher.ENCRYPT_MODE, publicKey); return byte2hex(cipher.doFinal(data.getBytes())); } /** * RSA解密 * * @param message * @param privateKey * @return */ public static String decryptRSA(String message, PrivateKey privateKey) throws Exception { byte[] bytes = hex2byte(message); Cipher cipher = Cipher.getInstance("RSA"); cipher.init(2, privateKey); byte[] result = cipher.doFinal(bytes); return new String(result); } /** * 16进制string转byte[] * * @param str * @return */ private static byte[] hex2byte(final String str) { if (str == null) { return new byte[]{}; } String newStr = str.trim(); int len = newStr.length(); if (len <= 0 || len % 2 != 0) { return new byte[]{}; } byte[] b = new byte[len / 2]; try { for (int i = 0; i < newStr.length(); i += 2) { b[i / 2] = (byte) Integer.decode("0x" + newStr.substring(i, i + 2)).intValue(); } return b; } catch (Exception e) { //NOSONAR return new byte[]{}; } } /** * byte[]转16进制string * * @param b * @return */ private static String byte2hex(byte[] b) { StringBuilder hs = new StringBuilder(); for (byte bi : b) { String temp = Integer.toHexString(bi & 0XFF); if (temp.length() == 1) { hs.append("0"); } hs.append(temp); } return hs.toString().toUpperCase(); } /** * MD5工具类 */ private static class MD5 { private static ThreadLocal threadLocal = new ThreadLocal() { @Override protected synchronized Object initialValue() { MessageDigest messagedigest = null; try { messagedigest = MessageDigest.getInstance("MD5"); } catch (NoSuchAlgorithmException var3) { } return messagedigest; } }; public MD5() { } public static MessageDigest getMessageDigest() { return (MessageDigest) threadLocal.get(); } public static String digest(String s, Charset charset) { getMessageDigest().update(s.getBytes(charset)); return HexUtil.bytes2Hexstr(getMessageDigest().digest()); } } }