文件加密及数据库字段加密

需求

不想服务器运维人员或管理员对服务器上的文件进行直接访问,可以得到内容。

不想数据库运维人员或管理员对数据库表中的内容直接查询得到。

代码

加密类依赖hutool

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
public class ByteArrayUtils {

/**
* 检查数组A是否包含数组B
*
* @param a 原始数组
* @param b 要查找的子数组
* @return 如果A包含B,则返回true;否则返回false
*/
public static boolean contains(byte[] a, byte[] b) {
if (a == null || b == null || a.length < b.length) {
return false;
}

for (int i = 0; i <= a.length - b.length; i++) {
int j;
for (j = 0; j < b.length; j++) {
if (a[i + j] != b[j]) {
break;
}
}
if (j == b.length) {
return true; // 找到了完整的匹配
}
}
return false; // 没有找到匹配
}

/**
* 检查数组A是否以数组B开头
*
* @param a 原始数组
* @param b 要检查是否作为开头的子数组
* @return 如果A以B开头,则返回true;否则返回false
*/
public static boolean startsWith(byte[] a, byte[] b) {
if (a == null || b == null || a.length < b.length) {
return false;
}

for (int i = 0; i < b.length; i++) {
if (a[i] != b[i]) {
return false; // 发现不匹配的元素
}
}

return true; // 所有元素都匹配,A以B开头
}

/**
* 在数组A前添加数组B
*
* @param b 要添加到前面的数组
* @param a 原始数组
* @return 合并后的新数组
*/
public static byte[] prepend(byte[] b, byte[] a) {
if (b == null) {
return a; // 如果b为空,则直接返回a
}
if (a == null) {
return b; // 如果a为空,则直接返回b
}

// 创建一个新的byte数组,长度为b和a的长度之和
byte[] result = new byte[b.length + a.length];

// 将b的元素复制到result的开头
System.arraycopy(b, 0, result, 0, b.length);

// 将a的元素复制到result的剩余部分
System.arraycopy(a, 0, result, b.length, a.length);

// 返回合并后的新数组
return result;
}

/**
* 从数组A中删除开头的数组B(如果A以B开头)
*
* 肯定在调用此方法之前调用了startsWith
*
* @param a 原始数组
* @param b 要从开头删除的数组
* @return 删除B后的新数组
*/
public static byte[] removeStart(byte[] a, byte[] b) {

// 创建一个新的byte数组,长度为a的长度减去b的长度
byte[] result = new byte[a.length - b.length];

// 将a中紧随b之后的元素复制到result中
System.arraycopy(a, b.length, result, 0, result.length);

// 返回删除B后的新数组
return result;
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
  
public class AESUtilWithPrefix {
private static AES getEncryptor(String secretKeyStr) {
// 构建 AES
AES aes = SecureUtil.aes(secretKeyStr.getBytes(StandardCharsets.UTF_8));
return aes;
}

public static String decrypt(String value, String prefix, String secretKeyStr) {
if (value == null) {
return null;
}
if (!StringUtils.hasText(value)) {
return "";
}
if (StringUtils.hasText(prefix)) {
if (!value.startsWith(prefix)) {
return value;
} else {
value = value.substring(prefix.length());
}
}
return getEncryptor(secretKeyStr).decryptStr(value);
}

public static String encrypt(String rawValue, String prefix, String secretKeyStr) {
if (rawValue == null) {
return null;
}
if (!StringUtils.hasText(rawValue)) {
return "";
}
String encryptStr = getEncryptor(secretKeyStr).encryptBase64(rawValue);
return prefix + encryptStr;
}

public static byte[] decrypt(byte[] value, String prefix, String secretKeyStr) {
if (value == null) {
return null;
}
if (value.length <= 0) {
return new byte[0];
}
byte[] needDecrypt = value;
if (StringUtils.hasText(prefix)) {
if (!ByteArrayUtils.startsWith(value, prefix.getBytes(StandardCharsets.UTF_8))) {
// 不以前缀开头,说明没有加密
return value;
} else {
needDecrypt = ByteArrayUtils.removeStart(value, prefix.getBytes(StandardCharsets.UTF_8));
}
}
return getEncryptor(secretKeyStr).decrypt(needDecrypt);
}

public static byte[] encrypt(byte[] rawValue, String prefix, String secretKeyStr) {
if (rawValue == null) {
return new byte[0];
}
if (rawValue.length <= 0) {
return new byte[0];
}
byte[] encrypt = getEncryptor(secretKeyStr).encrypt(rawValue);
return ByteArrayUtils.prepend(prefix.getBytes(StandardCharsets.UTF_8), encrypt);
}

public static void main(String[] args) {
String str = "helloword";
str = encrypt(str, "entrypted_", "1234567890123456");
System.out.println(str);
str = decrypt(str, "entrypted_", "1234567890123456");
System.out.println(str);
}

}

此工具类兼容不加密情况。

文件加解密

使用工具类中byte数组的加解密,对文件内容加解密,存储本地文件时加密,httpresponse响应文件时解密

数据库字段加解密

1
2
3
4
5
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface DecryptEncryptClass {

}
1
2
3
4
5
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface DecryptEncryptField {

}

查询解密方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
@Intercepts({ @Signature(type = ResultSetHandler.class, method = "handleResultSets", args = { Statement.class }) })
@Slf4j
public class DecryptInterceptor implements Interceptor {

@Value("${aes.enable:false}")
private Boolean aesEnable;

@Value("${aes.prefix:'entrypted_'}")
private String aesPrefix;

@Value("${aes.secretKeyStr:'1234567890123456'}")
private String aesSecretKeyStr;

@Override
public Object intercept(Invocation invocation) throws Throwable {
Object result = invocation.proceed();
if (result instanceof List) {
List<?> lists = (List<?>) result;
if (!lists.isEmpty()) {
if (Objects.nonNull(lists.get(0)) && lists.get(0).getClass().isAnnotationPresent(DecryptEncryptClass.class)) {
handlerResult(lists);
}
}
}
return result;
}

/**
* 处理结果
*/
private void handlerResult(List<?> lists) throws Exception {
try {
Map<String, Field> decMaps = new HashMap<>();
Class<?> clasz = lists.get(0).getClass();
Field[] fields = clasz.getDeclaredFields();
for (Field field : fields) {
if (field.isAnnotationPresent(DecryptEncryptField.class)) {
decMaps.put(field.getName(), field);
}
}
for (Object obj : lists) {
if (Objects.isNull(obj)) {
continue;
}
for (Map.Entry<String, Field> entity : decMaps.entrySet()) {
Field sourceField = entity.getValue();
handlerField(obj, sourceField);
}
}

} catch (Exception e) {
log.error("error", e);
throw e;
}
}

/**
* 处理加解密的字段
*/
private void handlerField(Object obj, Field sourceField) throws IllegalArgumentException, IllegalAccessException {
sourceField.setAccessible(true);
String sourceStr = (String) sourceField.get(obj);
if (StringUtils.isEmpty(sourceStr)) {
// 如果为空,不进行解密
return;
} else {
String str = AESUtilWithPrefix.decrypt(sourceStr, aesPrefix, aesSecretKeyStr);
sourceField.set(obj, str);
}

}

@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}

@Override
public void setProperties(Properties properties) {

}

}

修改方法加密

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
@Intercepts(
{
@Signature(type = Executor.class, method = "update", args = { MappedStatement.class, Object.class })
}
)
public class EncryptInterceptor implements Interceptor {

@Value("${aes.enable:false}")
private Boolean aesEnable;

@Value("${aes.prefix:'entrypted_'}")
private String aesPrefix;

@Value("${aes.secretKeyStr:'1234567890123456'}")
private String aesSecretKeyStr;

@Override
public Object intercept(Invocation invocation) throws Throwable {

Object[] args = invocation.getArgs();
Object parameter = args[1];

if (parameter instanceof Map<?, ?>) {
Map<?, ?> params = (Map<?, ?>) parameter;
for (Object param : params.values()) {
handleParameter(param);
}
} else {
handleParameter(parameter);
}

return invocation.proceed();
}

/**
* 反映修改参数中注解标注的属性值
*/
private void handleParameter(Object parameter) throws IllegalArgumentException, IllegalAccessException {
if (parameter != null && parameter.getClass().isAnnotationPresent(DecryptEncryptClass.class)) {
Field[] fields = parameter.getClass().getDeclaredFields();
// 加密
for (Field field : fields) {
if (field.isAnnotationPresent(DecryptEncryptField.class)) {
field.setAccessible(true);
String sourceVal = (String) field.get(parameter);
if (sourceVal == null) {
continue;
}
if (StringUtils.isEmpty(sourceVal)) {
continue;
}
if (sourceVal.startsWith(aesPrefix)) {
continue;
}
String encStr = AESUtilWithPrefix.encrypt(sourceVal, aesPrefix, aesSecretKeyStr);
field.setAccessible(true);
field.set(parameter, encStr);
}
}
}

}

@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}

@Override
public void setProperties(Properties properties) {

}

}

交给spring

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Configuration
public class MybatisConfig {

@Bean
public DecryptInterceptor decryptInterceptor() {
return new DecryptInterceptor();
}

@Bean
public EncryptInterceptor entryptInterceptor() {
return new EncryptInterceptor();
}

}

这里交给spring, 让spring 可以注入参数及 注入进mybatis的InterceptorChain中。

调试

看是否可注入进InterceptorChain一次,是否正确加解密。


文件加密及数据库字段加密
http://hanqichuan.com/2024/09/12/mybatis/文件加密及数据库字段加密/
作者
韩启川
发布于
2024年9月12日
许可协议