跳过导航

关于安全的知识点

xss 攻击原理

攻击者在网页中注入恶意脚本,使其在用户浏览器中自动执行,从而窃取信息或伪造操作

三种常见 XSS

① 存储型 XSS(最危险)

恶意代码存入数据库,所有访问页面的人都会中招。例:评论区、留言板、个人简介。 ② 反射型 XSS

恶意代码放在URL 参数里,诱骗用户点链接才触发。例:?keyword= ③ DOM 型 XSS

漏洞在前端 JS,数据不经过后端,直接从 URL 读到页面 DOM。例:前端直接用 location.search 拼到 HTML 里

java xss 防护方法

思路 通过关键字识别和替换来

package org.jeecg.common.util;

import org.springframework.web.util.HtmlUtils;

import java.util.regex.Pattern;

/**
 * XSS 攻击防护工具类
 * @author: jeecg-boot
 */
public class XssUtils {

    private static Pattern[] patterns = new Pattern[]{
        //Script fragments
        Pattern.compile("<script>(.*?)</script>", Pattern.CASE_INSENSITIVE),
        //src='...'
        Pattern.compile("src[\r\n]*=[\r\n]*\\\'(.*?)\\\'", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL),
        Pattern.compile("src[\r\n]*=[\r\n]*\\\"(.*?)\\\"", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL),
        //script tags
        Pattern.compile("</script>", Pattern.CASE_INSENSITIVE),
        Pattern.compile("<script(.*?)>", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL),
        //eval(...)
        Pattern.compile("eval\\((.*?)\\)", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL),
        //expression(...)
        Pattern.compile("e­xpression\\((.*?)\\)", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL),
        //javascript:...
        Pattern.compile("javascript:", Pattern.CASE_INSENSITIVE),
        //vbscript:...
        Pattern.compile("vbscript:", Pattern.CASE_INSENSITIVE),
        //onload(...)=...
        Pattern.compile("onload(.*?)=", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE | Pattern.DOTALL),
    };

    /**
     * 清理 XSS 攻击内容并进行 HTML 转义
     */
    public static String scriptXss(String value) {
        if (value != null) {
            // 注意:replaceAll(" ", "") 可能会破坏某些合法内容,这里保留原有逻辑但需谨慎使用
            // value = value.replaceAll(" ", ""); 
            for(Pattern scriptPattern: patterns){
                value = scriptPattern.matcher(value).replaceAll("");
            }
            return HtmlUtils.htmlEscape(value);
        }
        return null;
    }
}

sql注入

sql注入原理

sql 注入防护方法java

package org.jeecg.common.util;

import cn.hutool.core.util.ReUtil;
import lombok.extern.slf4j.Slf4j;
import org.jeecg.common.constant.CommonConstant;
import org.jeecg.common.constant.SymbolConstant;
import org.jeecg.common.exception.JeecgSqlInjectionException;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * sql注入处理工具类
 * 
 * @author zhoujf
 */
@Slf4j
public class SqlInjectionUtil {

	/**
	 * sql注入黑名单数据库名
	 */
	public final static String XSS_STR_TABLE = "peformance_schema|information_schema";

	/**
	 * 默认—sql注入关键词
	 */
	private final static String XSS_STR = "and |exec |peformance_schema|information_schema|extractvalue|updatexml|geohash|gtid_subset|gtid_subtract|insert |select |delete |update |drop |count |chr |mid |master |truncate |char |declare |;|or |+|--";
	/**
	 * online报表专用—sql注入关键词
	 */
	private static String specialReportXssStr = "exec |peformance_schema|information_schema|extractvalue|updatexml|geohash|gtid_subset|gtid_subtract|insert |alter |delete |grant |update |drop |master |truncate |declare |--";
	/**
	 * 字典专用—sql注入关键词
	 */
	private static String specialDictSqlXssStr = "exec |peformance_schema|information_schema|extractvalue|updatexml|geohash|gtid_subset|gtid_subtract|insert |select |delete |update |drop |count |chr |mid |master |truncate |char |declare |;|+|--";
	/**
	 * 完整匹配的key,不需要考虑前空格
	 */
	private static List<String> FULL_MATCHING_KEYWRODS = new ArrayList<>();
	static {
		FULL_MATCHING_KEYWRODS.add(";");
		FULL_MATCHING_KEYWRODS.add("+");
		FULL_MATCHING_KEYWRODS.add("--");
	}
	
	
	/**
	 * sql注入风险的 正则关键字
	 *
	 * 函数匹配,需要用正则模式
	 */
	private final static String[] XSS_REGULAR_STR_ARRAY = new String[]{
			"chr\\s*\\(",
			"mid\\s*\\(",
			" char\\s*\\(",
			"sleep\\s*\\(",
			"user\\s*\\(",
			"show\\s+tables",
			"user[\\s]*\\([\\s]*\\)",
			"show\\s+databases",
			"sleep\\(\\d*\\)",
			"sleep\\(.*\\)",
	};
	/**
	 * sql注释的正则
	 */
	private final static Pattern SQL_ANNOTATION = Pattern.compile("/\\*[\\s\\S]*\\*/");
	private final static  String SQL_ANNOTATION2 = "--";
	
	/**
	 * sql注入提示语
	 */
	private final static String SQL_INJECTION_KEYWORD_TIP = "请注意,存在SQL注入关键词---> {}";
	private final static String SQL_INJECTION_TIP = "请注意,值可能存在SQL注入风险!--->";
	private final static String SQL_INJECTION_TIP_VARIABLE = "请注意,值可能存在SQL注入风险!---> {}";
	

	/**
	 * sql注入过滤处理,遇到注入关键字抛异常
	 * @param values
	 */
	public static void filterContentMulti(String... values) {
		filterContent(values, null);
	}

	/**
	 * 校验比较严格
	 * 
	 * sql注入过滤处理,遇到注入关键字抛异常
	 *
	 * @param value
	 * @return
	 */
	public static void filterContent(String value, String customXssString) {
		if (value == null || "".equals(value)) {
			return;
		}
		// 一、校验sql注释 不允许有sql注释
		checkSqlAnnotation(value);
		// 转为小写进行后续比较
		value = value.toLowerCase().trim();
		
		// 二、SQL注入检测存在绕过风险 (普通文本校验)
		//https://gitee.com/jeecg/jeecg-boot/issues/I4NZGE
		String[] xssArr = XSS_STR.split("\\|");
		for (int i = 0; i < xssArr.length; i++) {
			if (value.indexOf(xssArr[i]) > -1) {
				log.error(SqlInjectionUtil.SQL_INJECTION_KEYWORD_TIP, xssArr[i]);
				log.error(SqlInjectionUtil.SQL_INJECTION_TIP_VARIABLE, value);
				throw new JeecgSqlInjectionException(SqlInjectionUtil.SQL_INJECTION_TIP + value);
			}
		}
		// 三、SQL注入检测存在绕过风险 (自定义传入普通文本校验)
		if (customXssString != null) {
			String[] xssArr2 = customXssString.split("\\|");
			for (int i = 0; i < xssArr2.length; i++) {
				if (value.indexOf(xssArr2[i]) > -1) {
					log.error(SqlInjectionUtil.SQL_INJECTION_KEYWORD_TIP, xssArr2[i]);
					log.error(SqlInjectionUtil.SQL_INJECTION_TIP_VARIABLE, value);
					throw new JeecgSqlInjectionException(SqlInjectionUtil.SQL_INJECTION_TIP + value);
				}
			}
		}

		// 四、SQL注入检测存在绕过风险 (正则校验)
		for (String regularOriginal : XSS_REGULAR_STR_ARRAY) {
			String regular = ".*" + regularOriginal + ".*";
			if (Pattern.matches(regular, value)) {
				log.error(SqlInjectionUtil.SQL_INJECTION_KEYWORD_TIP, regularOriginal);
				log.error(SqlInjectionUtil.SQL_INJECTION_TIP_VARIABLE, value);
				throw new JeecgSqlInjectionException(SqlInjectionUtil.SQL_INJECTION_TIP + value);
			}
		}
		return;
	}

	/**
	 * 判断是否存在SQL注入关键词字符串
	 *
	 * @param keyword
	 * @return
	 */
	@SuppressWarnings("AlibabaUndefineMagicConstant")
	private static boolean isExistSqlInjectKeyword(String sql, String keyword) {
		if (sql.startsWith(keyword.trim())) {
			return true;
		} else if (sql.contains(keyword)) {
			// 需要匹配的,sql注入关键词
			String matchingText = " " + keyword;
			if(FULL_MATCHING_KEYWRODS.contains(keyword)){
				matchingText = keyword;
			}
			
			if (sql.contains(matchingText)) {
				return true;
			} else {
				String regularStr = "\\s+\\S+" + keyword;
				List<String> resultFindAll = ReUtil.findAll(regularStr, sql, 0, new ArrayList<String>());
				for (String res : resultFindAll) {
					log.info("isExistSqlInjectKeyword —- 匹配到的SQL注入关键词:{}", res);
					/**
					 * SQL注入中可以替换空格的字符(%09  %0A  %0D  +都可以替代空格)
					 * http://blog.chinaunix.net/uid-12501104-id-2932639.html
					 * https://www.cnblogs.com/Vinson404/p/7253255.html
					 * */
					if (res.contains("%") || res.contains("+") || res.contains("#") || res.contains("/") || res.contains(")")) {
						return true;
					}
				}
			}
		}
		return false;
	}

	/**
	 * 判断是否存在SQL注入关键词字符串
	 *
	 * @param keyword
	 * @return
	 */
	@SuppressWarnings("AlibabaUndefineMagicConstant")
	private static boolean isExistSqlInjectTableKeyword(String sql, String keyword) {
		// 需要匹配的,sql注入关键词
		String[] matchingTexts = new String[]{"`" + keyword, "(" + keyword, "(`" + keyword};
		for (String matchingText : matchingTexts) {
			String[] checkTexts = new String[]{" " + matchingText, "from" + matchingText};
			for (String checkText : checkTexts) {
				if (sql.contains(checkText)) {
					return true;
				}
			}
		}
		return false;
	}

	/**
	 * sql注入过滤处理,遇到注入关键字抛异常
	 * 
	 * @param values
	 * @return
	 */
	public static void filterContent(String[] values, String customXssString) {
		for (String val : values) {
			if (oConvertUtils.isEmpty(val)) {
				return;
			}
			filterContent(val, customXssString);
		}
		return;
	}

	/**
	 * 【提醒:不通用】
	 * 仅用于字典条件SQL参数,注入过滤
	 *
	 * @param value
	 * @return
	 */
	public static void specialFilterContentForDictSql(String value) {
		String[] xssArr = specialDictSqlXssStr.split("\\|");
		if (value == null || "".equals(value)) {
			return;
		}
		// 一、校验sql注释 不允许有sql注释
		checkSqlAnnotation(value);
		value = value.toLowerCase().trim();
		
		// 二、SQL注入检测存在绕过风险 (普通文本校验)
		for (int i = 0; i < xssArr.length; i++) {
			if (isExistSqlInjectKeyword(value, xssArr[i])) {
				log.error(SqlInjectionUtil.SQL_INJECTION_KEYWORD_TIP, xssArr[i]);
				log.error(SqlInjectionUtil.SQL_INJECTION_TIP_VARIABLE, value);
				throw new JeecgSqlInjectionException(SqlInjectionUtil.SQL_INJECTION_TIP + value);
			}
		}
		String[] xssTableArr = XSS_STR_TABLE.split("\\|");
		for (String xssTableStr : xssTableArr) {
            if (isExistSqlInjectTableKeyword(value, xssTableStr)) {
                log.error(SqlInjectionUtil.SQL_INJECTION_KEYWORD_TIP, xssTableStr);
                log.error(SqlInjectionUtil.SQL_INJECTION_TIP_VARIABLE, value);
                throw new JeecgSqlInjectionException(SqlInjectionUtil.SQL_INJECTION_TIP + value);
            }
        }

		// 三、SQL注入检测存在绕过风险 (正则校验)
		for (String regularOriginal : XSS_REGULAR_STR_ARRAY) {
			String regular = ".*" + regularOriginal + ".*";
			if (Pattern.matches(regular, value)) {
				log.error(SqlInjectionUtil.SQL_INJECTION_KEYWORD_TIP, regularOriginal);
				log.error(SqlInjectionUtil.SQL_INJECTION_TIP_VARIABLE, value);
				throw new JeecgSqlInjectionException(SqlInjectionUtil.SQL_INJECTION_TIP + value);
			}
		}
		return;
	}

    /**
	 * 【提醒:不通用】
     *  仅用于Online报表SQL解析,注入过滤
     * @param value
     * @return
     */
	public static void specialFilterContentForOnlineReport(String value) {
		String[] xssArr = specialReportXssStr.split("\\|");
		if (value == null || "".equals(value)) {
			return;
		}
		// 一、校验sql注释 不允许有sql注释
		checkSqlAnnotation(value);
		value = value.toLowerCase().trim();
		
		// 二、SQL注入检测存在绕过风险 (普通文本校验)
		for (int i = 0; i < xssArr.length; i++) {
			if (isExistSqlInjectKeyword(value, xssArr[i])) {
				log.error(SqlInjectionUtil.SQL_INJECTION_KEYWORD_TIP, xssArr[i]);
				log.error(SqlInjectionUtil.SQL_INJECTION_TIP_VARIABLE, value);
				throw new JeecgSqlInjectionException(SqlInjectionUtil.SQL_INJECTION_TIP + value);
			}
		}
		String[] xssTableArr = XSS_STR_TABLE.split("\\|");
		for (String xssTableStr : xssTableArr) {
			if (isExistSqlInjectTableKeyword(value, xssTableStr)) {
				log.error(SqlInjectionUtil.SQL_INJECTION_KEYWORD_TIP, xssTableStr);
				log.error(SqlInjectionUtil.SQL_INJECTION_TIP_VARIABLE, value);
				throw new JeecgSqlInjectionException(SqlInjectionUtil.SQL_INJECTION_TIP + value);
			}
		}

		// 三、SQL注入检测存在绕过风险 (正则校验)
		for (String regularOriginal : XSS_REGULAR_STR_ARRAY) {
			String regular = ".*" + regularOriginal + ".*";
			if (Pattern.matches(regular, value)) {
				log.error(SqlInjectionUtil.SQL_INJECTION_KEYWORD_TIP, regularOriginal);
				log.error(SqlInjectionUtil.SQL_INJECTION_TIP_VARIABLE, value);
				throw new JeecgSqlInjectionException(SqlInjectionUtil.SQL_INJECTION_TIP + value);
			}
		}
		return;
	}


	/**
	 * 校验是否有sql注释 
	 * @return
	 */
	public static void checkSqlAnnotation(String str){
		if(str.contains(SQL_ANNOTATION2)){
			String error = "请注意,SQL中不允许含注释,有安全风险!";
			log.error(error);
			throw new RuntimeException(error);
		}

		
		Matcher matcher = SQL_ANNOTATION.matcher(str);
		if(matcher.find()){
			String error = "请注意,值可能存在SQL注入风险---> \\*.*\\";
			log.error(error);
			throw new JeecgSqlInjectionException(error);
		}
	}


	/**
	 * 返回查询表名
	 * <p>
	 * sql注入过滤处理,遇到注入关键字抛异常
	 *
	 * @param table
	 */
	private static Pattern tableNamePattern = Pattern.compile("^[a-zA-Z][a-zA-Z0-9_\\$]{0,63}$");
	public static String getSqlInjectTableName(String table) {
		if(oConvertUtils.isEmpty(table)){
			return table;
		}

		//update-begin---author:scott ---date:2024-05-28  for:表单设计器列表翻译存在表名带条件,导致翻译出问题----
		int index = table.toLowerCase().indexOf(" where ");
		if (index != -1) {
			table = table.substring(0, index);
			log.info("截掉where之后的新表名:" + table);
		}
		//update-end---author:scott ---date::2024-05-28  for:表单设计器列表翻译存在表名带条件,导致翻译出问题----

		table = table.trim();
		/**
		 * 检验表名是否合法
		 *
		 * 表名只能由字母、数字和下划线组成。
		 * 表名必须以字母开头。
		 * 表名长度通常有限制,例如最多为 64 个字符。
		 */
		boolean isValidTableName = tableNamePattern.matcher(table).matches();
		if (!isValidTableName) {
			String errorMsg = "表名不合法,存在SQL注入风险!--->" + table;
			log.error(errorMsg);
			throw new JeecgSqlInjectionException(errorMsg);
		}

		//进一步验证是否存在SQL注入风险
		filterContentMulti(table);
		return table;
	}


	/**
	 * 返回查询字段
	 * <p>
	 * sql注入过滤处理,遇到注入关键字抛异常
	 *
	 * @param field
	 */
	static final Pattern fieldPattern = Pattern.compile("^[a-zA-Z0-9_]+$");
	public static String getSqlInjectField(String field) {
		if(oConvertUtils.isEmpty(field)){
			return field;
		}
		
		field = field.trim();

		if (field.contains(SymbolConstant.COMMA)) {
			return getSqlInjectField(field.split(SymbolConstant.COMMA));
		}

		/**
		 * 校验表字段是否有效
		 *
		 * 字段定义只能是是字母 数字 下划线的组合(不允许有空格、转义字符串等)
		 */
		boolean isValidField = fieldPattern.matcher(field).matches();
		if (!isValidField) {
			String errorMsg = "字段不合法,存在SQL注入风险!--->" + field;
			log.error(errorMsg);
			throw new JeecgSqlInjectionException(errorMsg);
		}

		//进一步验证是否存在SQL注入风险
		filterContentMulti(field);
		return field;
	}

	/**
	 * 获取多个字段
	 * 返回: 逗号拼接
	 *
	 * @param fields
	 * @return
	 */
	public static String getSqlInjectField(String... fields) {
		for (String s : fields) {
			getSqlInjectField(s);
		}
		return String.join(SymbolConstant.COMMA, fields);
	}


	/**
	 * 获取排序字段
	 * 返回:字符串
	 *
	 * 1.将驼峰命名转化成下划线 
	 * 2.限制sql注入
	 * @param sortField  排序字段
	 * @return
	 */
	public static String getSqlInjectSortField(String sortField) {
		String field = SqlInjectionUtil.getSqlInjectField(oConvertUtils.camelToUnderline(sortField));
		return field;
	}

	/**
	 * 获取多个排序字段
	 * 返回:数组
	 *
	 * 1.将驼峰命名转化成下划线 
	 * 2.限制sql注入
	 * @param sortFields 多个排序字段
	 * @return
	 */
	public static List getSqlInjectSortFields(String... sortFields) {
		List list = new ArrayList<String>();
		for (String sortField : sortFields) {
			list.add(getSqlInjectSortField(sortField));
		}
		return list;
	}

	/**
	 * 获取 orderBy type
	 * 返回:字符串
	 * <p>
	 * 1.检测是否为 asc 或 desc 其中的一个
	 * 2.限制sql注入
	 *
	 * @param orderType
	 * @return
	 */
	public static String getSqlInjectOrderType(String orderType) {
		if (orderType == null) {
			return null;
		}
		orderType = orderType.trim();
		if (CommonConstant.ORDER_TYPE_ASC.equalsIgnoreCase(orderType)) {
			return CommonConstant.ORDER_TYPE_ASC;
		} else {
			return CommonConstant.ORDER_TYPE_DESC;
		}
	}

}

开放性重定向

开放式重定向防护



/**
 * @Description: 通用工具
 * @author: jeecg-boot
 */
@Slf4j
public class CommonUtils {
    /**
     * 安全重定向校验
     * @param url 待校验的URL
     * @param safeDomains 安全域名白名单
     * @return 是否安全
     */
    public static boolean isSafeUrl(String url, List<String> safeDomains) {
        if (StringUtils.isBlank(url)) {
            return false;
        }

        // 1. 站内相对路径校验 (以 / 开头,且不是 // 开头)
        if (url.startsWith("/") && !url.startsWith("//")) {
            return true;
        }

        // 2. 绝对路径校验
        try {
            URI uri = new URI(url);
            String host = uri.getHost();
            if (host == null) {
                return false;
            }

            // 校验域名是否在白名单中
            if (safeDomains != null && !safeDomains.isEmpty()) {
                for (String domain : safeDomains) {
                    if (host.equalsIgnoreCase(domain) || host.endsWith("." + domain)) {
                        return true;
                    }
                }
            }
        } catch (URISyntaxException e) {
            log.error("URL 格式错误: {}", url);
            return false;
        }

        return false;
    }
}

使用示例

//  securityProperties.getSafeRedirectDomains() 是配置的允许重定向的白名单 
// 校验重定向地址安全性
if (!CommonUtils.isSafeUrl(filePath, securityProperties.getSafeRedirectDomains())) {
    response.setStatus(403);
    return;
}
response.sendRedirect(filePath);

使用硬编码密码

代码里直接写 密码字符串了

整改方向

从代码中删除明文密码 移入配置文件 / 环境变量 / 密钥管理服务(Nacos/Apollo/vault) 代码读取配置获取密码

弱加密:使用不安全的ECB模式

# ECB / CBC 加密模式 通俗大白话详解
结合你刚才的 **DES、AES、PBE** 场景,专门讲透,秒懂区别、风险、整改要求。

---

## 一、基础前提
DES、AES 这类都属于**分组密码**
明文太长,会被切成**固定长度小块(分组)**
**ECB、CBC 就是:不同的「块与块之间怎么加密」的规则**

---

# 1. ECB 模式(电子密码本)
### 全称
Electronic CodeBook

### 工作逻辑
1. 把明文切成一块块
2. **每一块,单独用同一个密钥独立加密**
3. 块和块之间**毫无关联、互不影响**

### 致命缺点(重点!等保/渗透必高危)
1. **相同明文块 = 相同密文**
2. 完全没有混淆、没有随机性
3. 密文规律极强,**极易被破解、篡改、分析**

### 举个超级好懂的例子
明文:`AAAA BBBB AAAA`
ECB 加密后:`密A 密B 密A`
👉 一看就知道前后两段内容一样,直接泄露规律

### 结论
**ECB 极度不安全,现在所有安全规范:禁止使用**

---

# 2. CBC 模式(密码分组链接)
### 全称
Cipher Block Chaining

### 工作逻辑(核心:链式关联)
1. 明文切分成多个块
2. **第一个块**:先和一个随机的 **IV初始向量** 做异或,再加密
3. **后面每一个块**
   和「**上一块加密出来的密文**」异或 → 再加密
4. 块与块环环相扣、互相影响

### 优点
1. 相同明文,只要 IV 不同,**加密结果完全不一样**
2. 破坏规律、防分析、防明文猜测
3. 比 ECB 安全非常多

### 必要条件
**CBC 必须搭配:随机 IV(初始向量)**
- IV 长度 = 加密块长度(DES 64位 / AES 128位)
- IV 必须**随机、不可复用**

---

# 二、极简对比总结
| 特性 | ECB 模式 | CBC 模式 |
|------|----------|----------|
| 块关联 | 无,独立加密 | 链式关联,互相影响 |
| 相同明文 | 密文完全相同 | 密文完全不同 |
| 是否需要 IV | 不需要 | **必须要随机IV** |
| 安全性 | 极低,高危漏洞 | 中等(老旧可用) |
| 合规要求 | 禁止使用 | 可临时用,推荐升级 GCM |

---

# 三、结合你上个问题:PBEWithMD5AndDES
1. 老旧 Java 默认很多用:
   - **DES + ECB** → 超级高危
2. 安全整改最低要求:
   - 改成 **DES/CBC + 随机IV**
3. 最终最优整改:
   - 弃用 DES+MD5
   - 改用 **AES-GCM / AES-CBC + SHA256**

---

# 四、安全整改标准答案(你整改能用)
1. 严禁:
   - `DES/ECB``AES/ECB`
2. 过渡可用:
   - `AES/CBC/PKCS5Padding` + 随机IV
3. 最优推荐(等保/密评满分):
   - **AES-GCM**(自带完整性校验,防篡改)

---

# 五、一句话记忆
- **ECB:无脑单独加密,裸奔不安全,直接禁用**
- **CBC:前后块连环加密 + 随机IV,老系统标准安全方案**

需要我给你一段 Java 代码,演示 **ECB 不安全写法 vs CBC 安全写法** 对照吗?直接可用于整改替换。

整改代码

package org.jeecg.common.util.security;

import cn.hutool.core.codec.Base64Decoder;
import cn.hutool.core.codec.Base64Encoder;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.crypto.Mode;
import cn.hutool.crypto.Padding;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.crypto.asymmetric.KeyType;
import cn.hutool.crypto.asymmetric.RSA;
import cn.hutool.crypto.asymmetric.Sign;
import cn.hutool.crypto.asymmetric.SignAlgorithm;
import cn.hutool.crypto.symmetric.AES;
import org.jeecg.common.util.security.entity.*;
import com.alibaba.fastjson.JSONObject;
import javax.crypto.SecretKey;
import java.security.KeyPair;

/**
 * @Description: SecurityTools
 * @author: jeecg-boot
 */
public class SecurityTools {
    public static final String ALGORITHM = "AES/CBC/PKCS5Padding";
    private static final int IV_LENGTH = 16;
    private static final SignAlgorithm SIGN_ALGORITHM = SignAlgorithm.SHA256withRSA;

    public static SecurityResp valid(SecurityReq req) {
        SecurityResp resp=new SecurityResp();
        String pubKey=req.getPubKey();
        String aesKey=req.getAesKey();
        String data=req.getData();
        String signData=req.getSignData();
        RSA rsa=new RSA(null, Base64Decoder.decode(pubKey));
        Sign sign= new Sign(SIGN_ALGORITHM,null,pubKey);

        byte[] decryptAes = rsa.decrypt(aesKey, KeyType.PublicKey);
        // 解密出数据并提取 IV
        byte[] bytes = Base64Decoder.decode(data);
        if (bytes.length <= IV_LENGTH) {
            resp.setSuccess(false);
            return resp;
        }
        byte[] iv = ArrayUtil.sub(bytes, 0, IV_LENGTH);
        byte[] encrypted = ArrayUtil.sub(bytes, IV_LENGTH, bytes.length);

        AES aes = new AES(Mode.CBC, Padding.PKCS5Padding, decryptAes, iv);
        String dencrptValue = aes.decryptStr(encrypted);
        resp.setData(JSONObject.parseObject(dencrptValue));

        boolean verify = sign.verify(dencrptValue.getBytes(), Base64Decoder.decode(signData));
        resp.setSuccess(verify);
        return resp;
    }

    public static SecuritySignResp sign(SecuritySignReq req) {
        SecretKey secretKey = SecureUtil.generateKey("AES");
        byte[] key= secretKey.getEncoded();
        String prikey=req.getPrikey();
        String data=req.getData();

        // 生成随机 IV 并进行 CBC 加密
        byte[] iv = RandomUtil.randomBytes(IV_LENGTH);
        AES aes = new AES(Mode.CBC, Padding.PKCS5Padding, key, iv);
        byte[] encrypted = aes.encrypt(data);
        // 将 IV 和密文拼接后 Base64 编码
        byte[] result = ArrayUtil.addAll(iv, encrypted);
        String encrptData = Base64Encoder.encode(result);

        RSA rsa=new RSA(prikey,null);
        byte[] encryptAesKey = rsa.encrypt(secretKey.getEncoded(), KeyType.PrivateKey);

        Sign sign= new Sign(SIGN_ALGORITHM,prikey,null);
        byte[] signed = sign.sign(data.getBytes());

        SecuritySignResp resp=new SecuritySignResp();
        resp.setAesKey(Base64Encoder.encode(encryptAesKey));
        resp.setData(encrptData);
        resp.setSignData(Base64Encoder.encode(signed));
        return resp;
    }

    public static MyKeyPair generateKeyPair(){
        KeyPair keyPair= SecureUtil.generateKeyPair(SIGN_ALGORITHM.getValue(),2048);
        String priKey= Base64Encoder.encode(keyPair.getPrivate().getEncoded());
        String pubkey= Base64Encoder.encode(keyPair.getPublic().getEncoded());
        MyKeyPair resp=new MyKeyPair();
        resp.setPriKey(priKey);
        resp.setPubKey(pubkey);
        return resp;
    }
}