|
|
@@ -0,0 +1,233 @@
|
|
|
+package org.dromara.common.encrypt.utils;
|
|
|
+
|
|
|
+import cn.hutool.core.util.ObjectUtil;
|
|
|
+import cn.hutool.core.util.StrUtil;
|
|
|
+import cn.hutool.crypto.SecureUtil;
|
|
|
+import cn.hutool.crypto.asymmetric.KeyType;
|
|
|
+import cn.hutool.crypto.asymmetric.RSA;
|
|
|
+import cn.hutool.crypto.digest.MD5;
|
|
|
+import org.apache.commons.codec.digest.DigestUtils;
|
|
|
+import org.dromara.common.core.constant.CacheNames;
|
|
|
+import org.dromara.common.redis.utils.RedisUtils;
|
|
|
+
|
|
|
+import javax.crypto.Cipher;
|
|
|
+import javax.crypto.spec.SecretKeySpec;
|
|
|
+import java.nio.charset.StandardCharsets;
|
|
|
+
|
|
|
+/**
|
|
|
+ * name: YcEncryptUtil
|
|
|
+ * package: org.dromara.common.encrypt.utils
|
|
|
+ * description: 安全相关工具类
|
|
|
+ * date: 2024-08-17 22:27:12 22:27
|
|
|
+ *
|
|
|
+ * @author luoyibo
|
|
|
+ * @version 0.1
|
|
|
+ * @since JDK 1.8
|
|
|
+ */
|
|
|
+public class YcEncryptUtil extends EncryptUtils {
|
|
|
+ private static final String defaultCharset = "UTF-8";
|
|
|
+ private static final String KEY_AES = "AES";
|
|
|
+
|
|
|
+ // 加解密 默认向量
|
|
|
+ private static byte[] IVS = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D,
|
|
|
+ 0x0E, 0x0F};
|
|
|
+ // 加解密 默认密钥
|
|
|
+ private static byte[] KEYS = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D,
|
|
|
+ 0x0E, 0x0F};
|
|
|
+
|
|
|
+ /**
|
|
|
+ * rsa公钥分段解密,一卡通注册信息解密用
|
|
|
+ *
|
|
|
+ * @param data 待解密数据
|
|
|
+ * @param publicKey 公钥
|
|
|
+ * @return 解密后字符串
|
|
|
+ */
|
|
|
+ public static String decryptByRsaPublicKeySection(String data, String publicKey) {
|
|
|
+ if (StrUtil.isBlank(publicKey)) {
|
|
|
+ throw new IllegalArgumentException("RSA需要传入公钥进行解密");
|
|
|
+ }
|
|
|
+
|
|
|
+ RSA rsa = SecureUtil.rsa(null, hexToByte(publicKey));
|
|
|
+ return rsa.decryptStr(data, KeyType.PublicKey, StandardCharsets.UTF_8);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 生成Md5摘要计算结果
|
|
|
+ *
|
|
|
+ * @param key 待计算的数据
|
|
|
+ * @return 计算后的结果
|
|
|
+ */
|
|
|
+ public static String getMd5DigestData(String key) {
|
|
|
+ return MD5.create().digestHex(key);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 基于Md5摘要计算的AES加密
|
|
|
+ *
|
|
|
+ * @param md5key 客户公钥(对客户公钥进行Md5摘要计算后的16进制字符串)
|
|
|
+ * @param data 人员Id
|
|
|
+ * @return 加密后的结果
|
|
|
+ */
|
|
|
+ public static String encryptByMd5WidthAes(String data, String md5key) {
|
|
|
+ return encryptByMd5WidthAes(data, md5key, true);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 基于Md5摘要计算的AES加密
|
|
|
+ *
|
|
|
+ * @param md5key 客户公钥(对客户公钥进行Md5摘要计算后的16进制字符串)
|
|
|
+ * @param data 人员Id
|
|
|
+ * @param isPrefix 是否带前缀
|
|
|
+ * @return 加密后的结果
|
|
|
+ */
|
|
|
+ public static String encryptByMd5WidthAes(String data, String md5key, boolean isPrefix) {
|
|
|
+ try {
|
|
|
+ if (StrUtil.isEmpty(data) || StrUtil.isEmpty(md5key)) {
|
|
|
+ return "";
|
|
|
+ }
|
|
|
+ byte[] content;
|
|
|
+
|
|
|
+ content = data.getBytes(defaultCharset);
|
|
|
+
|
|
|
+ // 构造一个密钥
|
|
|
+ byte[] md5Keys = parseHexStr2Byte(md5key);
|
|
|
+ SecretKeySpec keySpec = new SecretKeySpec(md5Keys, KEY_AES);
|
|
|
+
|
|
|
+ // 创建密码器
|
|
|
+ Cipher cipher = Cipher.getInstance(KEY_AES);
|
|
|
+ // 初始化
|
|
|
+ cipher.init(Cipher.ENCRYPT_MODE, keySpec);
|
|
|
+ // 加密
|
|
|
+ byte[] result = cipher.doFinal(content);
|
|
|
+
|
|
|
+ return parseByte2HexStr(result, isPrefix);
|
|
|
+
|
|
|
+ } catch (Exception e) {
|
|
|
+ System.out.println("AES 密文处理异常:" + e.getMessage());
|
|
|
+ }
|
|
|
+ return "";
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 生成指定用户的卡余加密密钥
|
|
|
+ *
|
|
|
+ * @param publicKey 客户公钥(对客户公钥进行Md5摘要计算后的16进制字符串)
|
|
|
+ * @param data 人员Id
|
|
|
+ * @return 生成的密钥
|
|
|
+ */
|
|
|
+ public static String getBalanceSecretKey(String publicKey, String data) {
|
|
|
+ String secretKey;
|
|
|
+ String cacheKey = StrUtil.replace(CacheNames.USER_SECRET_KEY, "30d", data);
|
|
|
+ if (ObjectUtil.isNotNull(RedisUtils.getCacheObject(cacheKey))) {
|
|
|
+ secretKey = RedisUtils.getCacheObject(cacheKey).toString();
|
|
|
+ } else {
|
|
|
+ String md5DigestData = getMd5DigestData(publicKey);
|
|
|
+ secretKey = encryptByMd5WidthAes(data, md5DigestData, false);
|
|
|
+ RedisUtils.setCacheObject(cacheKey, secretKey);
|
|
|
+ }
|
|
|
+
|
|
|
+ return secretKey;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 加密卡余
|
|
|
+ *
|
|
|
+ * @param data 待加密余额
|
|
|
+ * @param userId 待加密余额的用户Id
|
|
|
+ * @param publicKey 客户公钥(对客户公钥进行Md5摘要计算后的16进制字符串)
|
|
|
+ * @return 加密后的余额(16进制字符串)
|
|
|
+ */
|
|
|
+ public static String encryptBagBalance(String data, String userId, String publicKey) {
|
|
|
+ String secretKey = getBalanceSecretKey(publicKey, userId);
|
|
|
+ byte[] key = hex(secretKey);
|
|
|
+
|
|
|
+ return SecureUtil.desede(key).encryptHex(data, StandardCharsets.UTF_8);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 解密卡余
|
|
|
+ *
|
|
|
+ * @param data 待解密余额
|
|
|
+ * @param userId 待解密余额的用户Id
|
|
|
+ * @param publicKey 客户公钥(对客户公钥进行Md5摘要计算后的16进制字符串)
|
|
|
+ * @return 解密后的余额(16进制字符串)
|
|
|
+ */
|
|
|
+ public static String decryptBagBalance(String data, String userId, String publicKey) {
|
|
|
+ String secretKey = getBalanceSecretKey(publicKey, userId);
|
|
|
+ byte[] key = hex(secretKey);
|
|
|
+
|
|
|
+ return SecureUtil.desede(key).decryptStr(data, StandardCharsets.UTF_8);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 16进制字符串转字节码
|
|
|
+ *
|
|
|
+ * @param key 16进制字符串
|
|
|
+ * @return 字节数组
|
|
|
+ */
|
|
|
+ public static byte[] hex(String key) {
|
|
|
+ String f = DigestUtils.md5Hex(key);
|
|
|
+ int enkLength = 24;
|
|
|
+ byte[] bKeys = f.getBytes();
|
|
|
+ byte[] enk = new byte[enkLength];
|
|
|
+ System.arraycopy(bKeys, 0, enk, 0, enkLength);
|
|
|
+ return enk;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 16进制字符串转字节码
|
|
|
+ *
|
|
|
+ * @param s 16进制字符串
|
|
|
+ * @return 字节数组
|
|
|
+ */
|
|
|
+ public static byte[] hexToByte(String s) {
|
|
|
+ int i = s.length() / 2;
|
|
|
+ byte[] bt = new byte[i];
|
|
|
+ for (int j = 0; j < i; ++j) {
|
|
|
+ String s1 = s.substring(j * 2, j * 2 + 2);
|
|
|
+ bt[j] = (byte) Integer.parseInt(s1, 16);
|
|
|
+ }
|
|
|
+ return bt;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 16进制字符串转字节码(高位在前,低位在后)
|
|
|
+ *
|
|
|
+ * @param hexStr 16进制字符串
|
|
|
+ * @return 字节数组
|
|
|
+ */
|
|
|
+ public static byte[] parseHexStr2Byte(String hexStr) {
|
|
|
+ if (StrUtil.isBlank(hexStr)) {
|
|
|
+ throw new IllegalArgumentException("待转换的16进制字符串丢失");
|
|
|
+ }
|
|
|
+
|
|
|
+ byte[] result = new byte[hexStr.length() / 2];
|
|
|
+ for (int i = 0; i < hexStr.length() / 2; i++) {
|
|
|
+ int high = Integer.parseInt(hexStr.substring(i * 2, i * 2 + 1), 16);
|
|
|
+ int low = Integer.parseInt(hexStr.substring(i * 2 + 1, i * 2 + 2), 16);
|
|
|
+ result[i] = (byte) (high * 16 + low);
|
|
|
+ }
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 将二进制转换成16进制,并且前面是否加前缀
|
|
|
+ *
|
|
|
+ * @param buf 待转换字节数组
|
|
|
+ * @param isPrefix 是否添加0x前缀
|
|
|
+ * @return 字节数组
|
|
|
+ */
|
|
|
+ public static String parseByte2HexStr(byte[] buf, boolean isPrefix) {
|
|
|
+ StringBuilder sb = new StringBuilder();
|
|
|
+ if (isPrefix)
|
|
|
+ sb.append("0x");
|
|
|
+ for (int i = 0; i < buf.length; i++) {
|
|
|
+ String hex = Integer.toHexString(buf[i] & 0xFF);
|
|
|
+ if (hex.length() == 1) {
|
|
|
+ hex = '0' + hex;
|
|
|
+ }
|
|
|
+ sb.append(hex.toUpperCase());
|
|
|
+ }
|
|
|
+ return sb.toString();
|
|
|
+ }
|
|
|
+}
|