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<String, Object> bizMap, PrivateKey privateKey, PublicKey publicKey, String funding) throws Exception {
|
// 业务报文
|
String params = JSON.toJSONString(bizMap);
|
// 时间戳
|
String timestamp = DateTimeFormatter.ofPattern("yyyyMMddHHmmss").format(LocalDateTime.now());
|
// 构建签名
|
SortedMap<String, String> 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<String, Object> 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<String, String> 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<String, Object> parseLVDiRequest(Map<String, String> 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<String, Object> 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<String, Object> bizMap, PrivateKey privateKey, PublicKey publicKey) throws Exception {
|
JSONObject json = new JSONObject();
|
TreeMap<String, String> 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<String, Object> bizMap, PrivateKey privateKey, PublicKey publicKey) throws Exception {
|
|
// 参数
|
String params = JSON.toJSONString(bizMap);
|
// 签名
|
TreeMap<String, String> 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<String, String> 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<String, String> map) {
|
StringBuilder sb = new StringBuilder();
|
Iterator<Map.Entry<String, String>> it = map.entrySet().iterator();
|
while (true) {
|
Map.Entry<String, String> 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());
|
}
|
}
|
|
|
}
|