This commit is contained in:
zhangwenzan 2025-09-17 14:17:20 +08:00
parent cfce3f4242
commit f80d75969d
5 changed files with 110 additions and 29 deletions

View File

@ -13,6 +13,7 @@ import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.Security;
import java.security.SecureRandom;
import java.util.Arrays;
import java.util.Base64;
@Slf4j
@ -102,4 +103,58 @@ public class EncryptionService {
throw new SecurityException("数据解密失败", e);
}
}
public String deterministicDecryptAes(String ciphertext) {
try {
// 使用与加密相同的固定IV
byte[] fixedIv = new byte[GCM_IV_LENGTH];
Arrays.fill(fixedIv, (byte) 0x01);
// 初始化密钥
SecretKey secretKey = new SecretKeySpec(Base64.getDecoder().decode(systemKey), AES_KEY_ALGORITHM);
// 初始化解密器
Cipher cipher = Cipher.getInstance(AES_ALGORITHM);
GCMParameterSpec parameterSpec = new GCMParameterSpec(GCM_TAG_LENGTH * 8, fixedIv);
cipher.init(Cipher.DECRYPT_MODE, secretKey, parameterSpec);
// 解码Base64密文注意这里不包含IV
byte[] decoded = Base64.getDecoder().decode(ciphertext);
// 执行解密
byte[] plaintext = cipher.doFinal(decoded);
return new String(plaintext, StandardCharsets.UTF_8);
} catch (Exception e) {
log.error("确定性AES解密失败", e);
throw new SecurityException("数据解密失败", e);
}
}
/**
* 确定性AES加密用于查询场景
* 使用固定IV确保相同明文总是生成相同密文
*/
public String deterministicEncryptAes(String plaintext) {
try {
// 使用固定IV注意在生产环境中应考虑使用派生IV而不是固定IV
byte[] fixedIv = new byte[GCM_IV_LENGTH];
// 可以使用字段名或其他确定性数据来派生IV
Arrays.fill(fixedIv, (byte) 0x01);
// 初始化密钥
SecretKey secretKey = new SecretKeySpec(Base64.getDecoder().decode(systemKey), AES_KEY_ALGORITHM);
// 初始化加密器
Cipher cipher = Cipher.getInstance(AES_ALGORITHM);
GCMParameterSpec parameterSpec = new GCMParameterSpec(GCM_TAG_LENGTH * 8, fixedIv);
cipher.init(Cipher.ENCRYPT_MODE, secretKey, parameterSpec);
// 执行加密
byte[] ciphertext = cipher.doFinal(plaintext.getBytes(StandardCharsets.UTF_8));
// 注意这里不包含IV因为IV是固定的
return Base64.getEncoder().encodeToString(ciphertext);
} catch (Exception e) {
log.error("确定性AES加密失败", e);
throw new SecurityException("数据加密失败", e);
}
}
}

View File

@ -49,7 +49,7 @@ public class SensitiveDataConverter extends AbstractJsonTypeHandler<String> impl
public void setNonNullParameter(PreparedStatement preparedStatement, int i, String s, JdbcType jdbcType) throws SQLException {
if (s != null && !s.isEmpty() && !s.startsWith(Const.ENCRYPTED_PREFIX)) {
// 加密后添加前缀标识
s = Const.ENCRYPTED_PREFIX + getEncryptionService().encryptAes(s);
s = Const.ENCRYPTED_PREFIX + getEncryptionService().deterministicEncryptAes(s);
}
preparedStatement.setString(i, s);
}

View File

@ -6,6 +6,7 @@ import com.kakarote.core.common.Const;
import com.kakarote.core.common.FieldEnum;
import com.kakarote.core.security.EncryptionService;
import com.kakarote.core.servlet.ApplicationContextHolder;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.CollectionUtils;
import java.util.*;
@ -13,6 +14,7 @@ import java.util.*;
/**
* Elasticsearch数据加密解密工具类
*/
@Slf4j
public class EsDataEncryptUtil {
private static EncryptionService encryptionService;
@ -110,7 +112,7 @@ public class EsDataEncryptUtil {
String strValue = (String) value;
// 已经加密的不再重复加密
if (!strValue.isEmpty() && !strValue.startsWith(Const.ENCRYPTED_PREFIX)) {
return Const.ENCRYPTED_PREFIX + getEncryptionService().encryptAes(strValue);
return Const.ENCRYPTED_PREFIX + getEncryptionService().deterministicEncryptAes(strValue);
}
return strValue;
} else if (value instanceof Map) {
@ -132,7 +134,7 @@ public class EsDataEncryptUtil {
* 根据值类型进行解密
*/
@SuppressWarnings("unchecked")
private static Object decryptValue(String key,Object value) {
private static Object decryptValue(String key, Object value) {
if (value == null) {
return null;
}
@ -141,8 +143,17 @@ public class EsDataEncryptUtil {
String strValue = (String) value;
// 对已加密的数据进行解密
if (strValue.startsWith(Const.ENCRYPTED_PREFIX)) {
String newValue = getEncryptionService().decryptAes(strValue.substring(Const.ENCRYPTED_PREFIX.length()));
return desensitization(key,newValue);
String encryptedValue = strValue.substring(Const.ENCRYPTED_PREFIX.length());
// 尝试使用确定性解密用于查询和存储中使用了deterministicEncryptAes的情况
try {
String newValue = getEncryptionService().deterministicDecryptAes(encryptedValue);
return desensitization(key, newValue);
} catch (Exception e) {
// 如果确定性解密失败尝试使用普通解密
log.debug("确定性解密失败,尝试普通解密", e);
String newValue = getEncryptionService().decryptAes(encryptedValue);
return desensitization(key, newValue);
}
}
return strValue;
} else if (value instanceof Map) {
@ -152,13 +163,13 @@ public class EsDataEncryptUtil {
// 递归处理List中的每个元素
List<Object> decryptedList = new ArrayList<>();
for (Object item : (List<Object>) value) {
decryptedList.add(decryptValue(key,item));
decryptedList.add(decryptValue(key, item));
}
return decryptedList;
}
// 其他类型保持不变
return value;
}
}
// 数据脱敏
private static String desensitization(String key,String value){

View File

@ -255,7 +255,7 @@ public class ElasticUtil {
if (value instanceof String) {
String strValue = (String) value;
if (!strValue.isEmpty() && !strValue.startsWith(Const.ENCRYPTED_PREFIX)) {
map.put(fieldName, Const.ENCRYPTED_PREFIX + ApplicationContextHolder.getBean(EncryptionService.class).encryptAes(strValue));
map.put(fieldName, Const.ENCRYPTED_PREFIX + ApplicationContextHolder.getBean(EncryptionService.class).deterministicEncryptAes(strValue));
} else {
map.put(fieldName, value);
}
@ -303,10 +303,23 @@ public class ElasticUtil {
if (search.getValues().size() == 0 && !Arrays.asList(5, 6).contains(search.getSearchEnum().getType())) {
return;
}
// 对需要加密的字段值进行加密处理
List<String> encryptedValues = new ArrayList<>();
for (String value : search.getValues()) {
if (!value.isEmpty() && !value.startsWith(Const.ENCRYPTED_PREFIX)) {
// 添加加密前缀并进行AES加密
encryptedValues.add(Const.ENCRYPTED_PREFIX + ApplicationContextHolder.getBean(EncryptionService.class).deterministicEncryptAes(value));
} else {
encryptedValues.add(value);
}
}
search.setValues(encryptedValues);
switch (search.getSearchEnum()) {
case IS:
queryBuilder.filter(QueryBuilders.termsQuery(search.getName(), search.getValues()));
break;
// 其他搜索条件处理保持不变
case IS_NOT:
queryBuilder.mustNot(QueryBuilders.termsQuery(search.getName(), search.getValues()));
break;

View File

@ -16,6 +16,8 @@ public class CrmQueryGsdjxxDTO implements Serializable {
// 登记序号
private String yf;
private String rq;
// 评价年度
private String xzqhDm;