package com.nova.sankuai.infra.utils;
|
|
import cn.hutool.core.util.IdUtil;
|
import com.alibaba.fastjson.JSON;
|
import com.alibaba.fastjson.JSONObject;
|
import com.alibaba.fastjson.TypeReference;
|
import org.apache.commons.codec.binary.Base64;
|
import org.apache.commons.lang3.RandomStringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
import org.slf4j.Logger;
|
import org.slf4j.LoggerFactory;
|
|
import javax.crypto.Cipher;
|
import javax.crypto.spec.IvParameterSpec;
|
import javax.crypto.spec.SecretKeySpec;
|
import java.io.ByteArrayOutputStream;
|
import java.nio.charset.StandardCharsets;
|
import java.security.*;
|
import java.security.interfaces.RSAPrivateKey;
|
import java.security.interfaces.RSAPublicKey;
|
import java.security.spec.PKCS8EncodedKeySpec;
|
import java.security.spec.X509EncodedKeySpec;
|
import java.util.*;
|
import java.util.regex.Matcher;
|
import java.util.regex.Pattern;
|
import java.util.stream.Collectors;
|
|
public class RbUtil {
|
/**
|
* 密钥长度 于原文长度对应 以及越长速度越慢
|
*/
|
private final static int KEY_SIZE = 1024;
|
private static final int MAX_ENCRYPT_BLOCK = 117;
|
private static final int MAX_DECRYPT_BLOCK = 128;
|
private static final String KEY_ALGORITHM = "RSA";
|
private static final String SIGNATURE_ALGORITHM = "SHA256WithRSA";
|
private static final Logger log = LoggerFactory.getLogger("capitalLogger");
|
private static final String CHARSET_NAME = String.valueOf(StandardCharsets.UTF_8);
|
public static final String AES_CBC_PKCS5PADDING = "AES/CBC/PKCS5PADDING";
|
|
/**
|
* 创建RSA公钥和私钥对
|
* <p>
|
* 公钥:RSAUtils.PUBLIC_KEY
|
* 私钥:RSAUtils.PRIVATE_KEY
|
*
|
* @throws NoSuchAlgorithmException 创建异常
|
*/
|
private static void newRsaKeys() throws NoSuchAlgorithmException {
|
KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance(KEY_ALGORITHM);
|
keyPairGen.initialize(KEY_SIZE);
|
KeyPair keyPair = keyPairGen.generateKeyPair();
|
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
|
RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
|
String publicKeyStr = Base64.encodeBase64String(publicKey.getEncoded());
|
String privateKeyStr = Base64.encodeBase64String(privateKey.getEncoded());
|
String logNumber = IdUtil.randomUUID();
|
log.info("日志编号:{}, RSA::PrivateKey {}", logNumber, privateKeyStr);
|
log.info("日志编号:{}, RSA::PublicKey {}", logNumber, publicKeyStr);
|
}
|
|
/**
|
* 响应结果
|
* <p>
|
* 解密响应数据及验签
|
*
|
* @param response 响应数据
|
* @param sign 签名, 响应数据中的sign字段
|
* @param key aes密钥, 请求体中的key字段
|
* @param data 密文业务数据, 响应数据中的data字段
|
* @param rsaPublicKey rsa公钥
|
* @param rsaPrivateKey rsa私钥
|
* @return 解密后的业务数据
|
*/
|
public static String decrypt(Object response, String sign, String key, String data, String rsaPublicKey, String rsaPrivateKey) {
|
// 验签
|
HashMap map = JSONObject.parseObject(JSON.toJSON(response).toString(), HashMap.class);
|
String signContent = getSignContent(map);
|
boolean isSignCorrect = verifySignByPublicKey(signContent, sign, rsaPublicKey);
|
String logNumber = IdUtil.randomUUID();
|
log.info("日志编号:{}, decrypt: {}, isSignCorrect: {}", logNumber, signContent, isSignCorrect);
|
if (!isSignCorrect) {
|
throw new RuntimeException("验签失败");
|
}
|
try {
|
// 使用私钥解密得到AES秘钥
|
String aesKey = decryptByPrivateKey(key, rsaPrivateKey);
|
log.info("日志编号:{}, aesKey: {}", logNumber, aesKey);
|
// 使用AES秘钥解密params得到业务参数
|
String decryptStr = aesDecrypt(data, aesKey);
|
log.info("日志编号:{}, decryptStr: {}", logNumber, decryptStr);
|
return decryptStr;
|
} catch (Exception e) {
|
throw new RuntimeException("解密失败", e);
|
}
|
}
|
|
/**
|
* 请求接口
|
* <p>
|
* 加密请求参数及加签
|
*
|
* @param channel 渠道方名称
|
* @param t 系统时间
|
* @param data 加密前的业务数据
|
* @param rsaPublicKey rsa公钥
|
* @param rsaPrivateKey rsa私钥
|
* @return 加密后的请求数据
|
*/
|
public static String encrypt(String channel, Long t, String data, String rsaPublicKey, String rsaPrivateKey) {
|
// 随机字符串 作为 “AES秘钥” (注意AES加解密128/192/256 bits.对应秘钥16/24/32位)
|
String aesKey = RandomStringUtils.randomAlphanumeric(16);
|
String params, key;
|
try {
|
// 使用AES秘钥加密得到data
|
params = aesEncrypt(data, aesKey);
|
// 使用RSA公钥 加密 “AES秘钥” 得到 key
|
key = rsaEncryptWithPublicKeyToBase64(getPublicKey(rsaPublicKey), aesKey.getBytes(CHARSET_NAME));
|
} catch (Exception e) {
|
throw new RuntimeException("加密失败", e);
|
}
|
// 组装数据
|
Map<String, String> request = new HashMap<>(5, 1F);
|
request.put("channel", channel);
|
request.put("t", String.valueOf(t));
|
request.put("params", params);
|
request.put("key", key);
|
// 加签
|
String signContent = getSignContent(request);
|
String sign = signByPrivateKey(signContent, rsaPrivateKey);
|
request.put("sign", sign);
|
|
return JSON.toJSONString(request);
|
}
|
|
/**
|
* 验签 及 解密请求数据
|
*
|
* @param request 请求数据
|
* @param sign 签名, 请求数据中的sign字段
|
* @param key aes密钥, 请求数据中的key字段
|
* @param params 密文业务数据, 请求数据中的params字段
|
* @param rsaPublicKey rsa公钥
|
* @param rsaPrivateKey rsa私钥
|
* @return 解密后的业务数据
|
*/
|
public static String decryptRequest(Object request, String sign, String key, String params, String rsaPublicKey, String rsaPrivateKey) {
|
// 验签
|
String signContent = getSignContent(JSON.parseObject(JSON.toJSONString(request), new TypeReference<Map<String, String>>() {}));
|
boolean isSignCorrect = verifySignByPublicKey(signContent, sign, rsaPublicKey);
|
if (!isSignCorrect) {
|
throw new RuntimeException("验签失败");
|
}
|
|
try {
|
// 使用私钥解密得到AES秘钥
|
String aesKey = decryptByPrivateKey(key, rsaPrivateKey);
|
// 使用AES秘钥解密params得到业务参数
|
return aesDecrypt(params, aesKey);
|
} catch (Exception e) {
|
throw new RuntimeException("解密失败", e);
|
}
|
}
|
|
/**
|
* 加密 响应数据 及 加签
|
*
|
* @param code 返回码, 响应体中的code字段
|
* @param msg 响应信息, 响应体中的msg字段
|
* @param responseData 加密前的业务数据
|
* @param rsaPublicKey rsa公钥
|
* @param rsaPrivateKey rsa私钥
|
* @return 加密后的响应数据
|
*/
|
public static String encryptResponse(String code, String msg, String responseData, String rsaPublicKey, String rsaPrivateKey) {
|
// 随机字符串 作为 “AES秘钥” (注意AES加解密128/192/256 bits.对应秘钥16/24/32位)
|
String aesKey = RandomStringUtils.randomAlphanumeric(16);
|
String logNumber = IdUtil.randomUUID();
|
log.info("日志编号:{}, RSA::aesKey {}", logNumber, aesKey);
|
String data, key;
|
try {
|
// 使用AES秘钥加密得到data
|
data = aesEncrypt(responseData, aesKey);
|
// 使用RSA公钥 加密 “AES秘钥” 得到 key
|
log.info("日志编号:{}, RSA::rsaPublicKey {}", logNumber, rsaPublicKey);
|
key = rsaEncryptWithPublicKeyToBase64(getPublicKey(rsaPublicKey), aesKey.getBytes(CHARSET_NAME));
|
log.info("日志编号:{}, RSA::key {}", logNumber, key);
|
} catch (Exception e) {
|
log.error("日志编号:{}, {}", logNumber, e.getMessage());
|
throw new RuntimeException("加密失败", e);
|
}
|
// 组装数据
|
Map<String, String> response = new HashMap<>(5, 1F);
|
response.put("code", code);
|
response.put("msg", msg);
|
response.put("data", data);
|
response.put("key", key);
|
|
log.info("日志编号:{}, RSA::rsaPrivateKey {}", logNumber, rsaPrivateKey);
|
|
// 加签
|
String signContent = getSignContent(response);
|
String sign = signByPrivateKey(signContent, rsaPrivateKey);
|
response.put("sign", sign);
|
|
return JSON.toJSONString(response);
|
}
|
|
/**
|
* 对签名转换升序排序
|
*/
|
private static String getSignContent(Map<String, String> paramObj) {
|
TreeMap<String, String> treeMap = convertMapToTreeMap(paramObj);
|
return treeMap.entrySet().stream()
|
.map(entry -> {
|
if (!"sign".equals(entry.getKey())) {
|
if (StringUtils.isNotEmpty(entry.getValue()) && !"null".equalsIgnoreCase(entry.getValue())) {
|
return entry.getKey() + "=" + entry.getValue();
|
}
|
}
|
return "";
|
})
|
.collect(Collectors.joining("&"));
|
}
|
|
private static TreeMap<String, String> convertMapToTreeMap(Map<String, String> paramMap) {
|
TreeMap<String, String> treeMap = new TreeMap<>();
|
if (paramMap == null) {
|
return treeMap;
|
}
|
treeMap.putAll(paramMap);
|
// 本身的签名信息不参与签名
|
treeMap.remove("sign");
|
return treeMap;
|
}
|
|
private static String signByPrivateKey(String content, String privateKey) {
|
try {
|
PrivateKey priKey = getPrivateKey(privateKey);
|
Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
|
signature.initSign(priKey);
|
signature.update(content.getBytes(CHARSET_NAME));
|
byte[] signed = signature.sign();
|
return new String(Base64.encodeBase64(signed), CHARSET_NAME);
|
} catch (Exception e) {
|
log.info("RSA::rsaPrivateKey {}", e);
|
throw new RuntimeException("加签失败");
|
}
|
}
|
|
private static boolean verifySignByPublicKey(String content, String sign, String publicKey) {
|
try {
|
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
|
byte[] encodedKey = Base64.decodeBase64(publicKey.getBytes(CHARSET_NAME));
|
PublicKey pubKey = keyFactory.generatePublic(new X509EncodedKeySpec(encodedKey));
|
Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
|
signature.initVerify(pubKey);
|
signature.update(content.getBytes(CHARSET_NAME));
|
return signature.verify(Base64.decodeBase64(sign.getBytes(CHARSET_NAME)));
|
} catch (Exception e) {
|
throw new RuntimeException("验签失败");
|
}
|
}
|
|
/**
|
* 获取私钥
|
*/
|
private static PrivateKey getPrivateKey(String key) throws Exception {
|
byte[] keyBytes = Base64.decodeBase64(key);
|
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes);
|
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
|
return keyFactory.generatePrivate(keySpec);
|
}
|
|
/**
|
* 获取公钥
|
*/
|
private static PublicKey getPublicKey(String key) throws Exception {
|
byte[] keyBytes = Base64.decodeBase64(key);
|
X509EncodedKeySpec keySpec = new X509EncodedKeySpec(keyBytes);
|
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
|
return keyFactory.generatePublic(keySpec);
|
}
|
|
private static String aesEncrypt(String content, String key) throws Exception {
|
Cipher cipher = Cipher.getInstance(AES_CBC_PKCS5PADDING);
|
byte[] keyBytes = key.getBytes(CHARSET_NAME);
|
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(keyBytes, "AES"));
|
byte[] bytes = cipher.doFinal(content.getBytes(CHARSET_NAME));
|
return Base64.encodeBase64String(bytes);
|
}
|
|
/**
|
* Aes 加密方法
|
* Aes 加密再 Base64 加密
|
* 算法/模式/补码方式 AES/CBC/PKCS5Padding
|
*
|
* @param content 加密内容
|
* @param key 密钥(密码)
|
* @param initVector 偏移量
|
* @return 加密后的数据
|
*/
|
public static String aesEncrypt(String content, String key, String initVector) throws Exception {
|
Cipher cipher = Cipher.getInstance(AES_CBC_PKCS5PADDING);
|
IvParameterSpec iv = new IvParameterSpec(initVector.getBytes(CHARSET_NAME));
|
byte[] keyBytes = key.getBytes(CHARSET_NAME);
|
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(keyBytes, "AES"), iv);
|
byte[] bytes = cipher.doFinal(content.getBytes(CHARSET_NAME));
|
return Base64.encodeBase64String(bytes);
|
}
|
|
private static String rsaEncryptWithPublicKeyToBase64(PublicKey publicKey, byte[] rawData) throws Exception {
|
final byte[] encryptBytes = encryptBytes(publicKey, rawData, Cipher.ENCRYPT_MODE);
|
return Base64.encodeBase64String(encryptBytes);
|
}
|
|
private static byte[] encryptBytes(Key key, byte[] rawData, int encryptMode) throws Exception {
|
String algorithm = key.getAlgorithm();
|
final Cipher cipher = Cipher.getInstance(algorithm.toUpperCase());
|
cipher.init(encryptMode, key);
|
return cipher.doFinal(rawData);
|
}
|
|
private static String decryptByPrivateKey(String value, String key) throws Exception {
|
byte[] data = Base64.decodeBase64(value);
|
// 对私钥解密
|
byte[] keyBytes = Base64.decodeBase64(key);
|
PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(keyBytes);
|
KeyFactory keyFactory = KeyFactory.getInstance(KEY_ALGORITHM);
|
Key privateKey = keyFactory.generatePrivate(pkcs8EncodedKeySpec);
|
// 对数据解密
|
Cipher cipher = Cipher.getInstance(keyFactory.getAlgorithm());
|
cipher.init(Cipher.DECRYPT_MODE, privateKey);
|
return new String(doFinalBySegment(cipher, data, false), CHARSET_NAME);
|
}
|
|
private static byte[] doFinalBySegment(Cipher cipher, byte[] source, boolean isEncode) throws Exception {
|
try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
|
int blockSize = isEncode ? MAX_ENCRYPT_BLOCK : MAX_DECRYPT_BLOCK;
|
if (source.length <= blockSize) {
|
return cipher.doFinal(source);
|
}
|
int offsetIndex = 0, offset = 0, sourceLength = source.length;
|
while (sourceLength - offset > 0) {
|
int size = Math.min(sourceLength - offset, blockSize);
|
byte[] buffer = cipher.doFinal(source, offset, size);
|
out.write(buffer, 0, buffer.length);
|
offsetIndex++;
|
offset = offsetIndex * blockSize;
|
}
|
return out.toByteArray();
|
}
|
}
|
|
/**
|
* aes解密
|
*
|
* @param encryptBytes 密文
|
* @param key 秘钥
|
*/
|
private static String aesDecrypt(String encryptBytes, String key) throws Exception {
|
Cipher cipher = Cipher.getInstance(AES_CBC_PKCS5PADDING);
|
byte[] keyBytes = key.getBytes(CHARSET_NAME);
|
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(keyBytes, "AES"));
|
byte[] decryptBytes = cipher.doFinal(Base64.decodeBase64(encryptBytes));
|
return new String(decryptBytes);
|
}
|
|
/**
|
* Aes 解密方法
|
* Base64 解密再 Aes 解密
|
* 算法/模式/补码方式 AES/CBC/PKCS5Padding
|
*
|
* @param encryptBytes 解密内容
|
* @param key 密钥(密码)
|
* @param initVector 偏移量
|
* @return 解密后的数据
|
*/
|
public static String aesDecrypt(String encryptBytes, String key, String initVector) throws Exception {
|
Cipher cipher = Cipher.getInstance(AES_CBC_PKCS5PADDING);
|
IvParameterSpec iv = new IvParameterSpec(initVector.getBytes(CHARSET_NAME));
|
byte[] keyBytes = key.getBytes(CHARSET_NAME);
|
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(keyBytes, "AES"), iv);
|
byte[] decryptBytes = cipher.doFinal(Base64.decodeBase64(encryptBytes));
|
return new String(decryptBytes);
|
}
|
|
/**
|
* 根据身份证号码获取年龄
|
* @param idNumber
|
* @return
|
*/
|
public static int calculateAgeFromIdNumber(String idNumber) {
|
if (idNumber == null || idNumber.length() != 18) {
|
throw new IllegalArgumentException("身份证号码不正确");
|
}
|
|
// 获取出生年份
|
String birthYearStr = idNumber.substring(6, 10);
|
// 获取当前年份的字符串
|
String currentYearStr = String.valueOf(Calendar.getInstance().get(Calendar.YEAR));
|
|
// 计算年龄
|
int age = Integer.parseInt(currentYearStr) - Integer.parseInt(birthYearStr);
|
if (age <= 21 || age >=56) {
|
throw new IllegalArgumentException("年龄不符合条件");
|
}
|
return age;
|
}
|
|
|
/**
|
* 根据身份证号码判断性别
|
* @param idCard
|
* @return
|
*/
|
public static String getGenderByIDCard(String idCard) {
|
if (idCard == null || (idCard.length() != 18 && idCard.length() != 15)) {
|
throw new IllegalArgumentException("身份证号码长度不正确");
|
}
|
char sex = idCard.charAt(idCard.length() - 2);
|
return (sex % 2 == 0) ? "女性" : "男性";
|
}
|
|
|
/**
|
* 身份证地址提取省市区
|
*
|
* @param fullAddress 身份证完整地址
|
*/
|
public static Map<String, String> addressResolution(String fullAddress) {
|
// 定义正则
|
String regex = "(?<province>[^省]+自治区|.*?省|.*?行政区|.*?市)(?<city>[^市]+自治州|.*?地区|.*?行政单位|.+盟|市辖区|.*?市|.*?县)(?<area>[^县]+县|.+区|.+市|.+旗|.+海域|.+岛)?(?<town>[^区]+区|.+镇)?(?<detail>.*)";
|
|
Matcher matcher = Pattern.compile(regex).matcher(fullAddress);
|
String province, city, area, town, detail;
|
Map<String, String> map = new LinkedHashMap<>();
|
|
while (matcher.find()) {
|
province = matcher.group("province");
|
map.put("province", StringUtils.isEmpty(province) ? "" : province.trim());
|
|
city = matcher.group("city");
|
map.put("city", StringUtils.isEmpty(city) ? "" : city.trim());
|
|
area = matcher.group("area");
|
map.put("area", StringUtils.isEmpty(area) ? "" : area.trim());
|
|
town = matcher.group("town");
|
map.put("town", StringUtils.isEmpty(town) ? "" : town.trim());
|
|
detail = matcher.group("detail");
|
map.put("detail", StringUtils.isEmpty(detail) ? "" : detail.trim());
|
}
|
return map;
|
}
|
}
|