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公钥和私钥对 *

* 公钥: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); } /** * 响应结果 *

* 解密响应数据及验签 * * @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); } } /** * 请求接口 *

* 加密请求参数及加签 * * @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 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>() {})); 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 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 paramObj) { TreeMap 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 convertMapToTreeMap(Map paramMap) { TreeMap 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 addressResolution(String fullAddress) { // 定义正则 String regex = "(?[^省]+自治区|.*?省|.*?行政区|.*?市)(?[^市]+自治州|.*?地区|.*?行政单位|.+盟|市辖区|.*?市|.*?县)(?[^县]+县|.+区|.+市|.+旗|.+海域|.+岛)?(?[^区]+区|.+镇)?(?.*)"; Matcher matcher = Pattern.compile(regex).matcher(fullAddress); String province, city, area, town, detail; Map 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; } }