AES加解密
# 1.时序图

# 2.流程说明
1.后端分配key
2.浏览器发起请求时,获取当前时间戳作为盐值(salt)进行AES加密
3.加密成AES后,需要再加密成BASE64传入后端,否则可能因为加密串的问题会丢失信息。
4.同时需要把时间戳放入请求头(timestamp)
5.然后后端获取消息体进行BASE64解密
6.再获取请求头的timestamp+可以进行AES256解密
7.处理完成之后根据请求头的timestamp进行AES加密和BASE64加密
8.然后返回前端,前端再进行BASE64解密和AES解密
$缺陷加解密耗时较长,大概2秒左右
后续改成了无盐值模式,只是分配key,几十毫秒即可完成加解密。
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
# 3.代码实现
# 3.1加解密工具类
import org.apache.commons.codec.binary.Base64;
import javax.crypto.Cipher;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.UnsupportedEncodingException;
import java.security.NoSuchAlgorithmException;
public class AESEncrypt {
public static final String UTF_8 = "UTF-8";
private static final String ALGORITHM = "AES/CBC/PKCS5Padding";
private static final byte[] iv = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
static {
try {
//预热,可以加快加解密速度
Cipher.getInstance(ALGORITHM);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (NoSuchPaddingException e) {
e.printStackTrace();
}
}
/**
* 生成 SecretKey
*
* @param secret
* @param salt
* @return
*/
public static SecretKey generateSecretKey(String secret, String salt) {
SecretKey secretKey = null;
try {
//用盐值的模式,耗时将近2秒
SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
PBEKeySpec keySpec = new PBEKeySpec(secret.toCharArray(), salt.getBytes(), 65536, 256);
secretKey = new SecretKeySpec(factory.generateSecret(keySpec).getEncoded(), "AES");
} catch (Exception e) {
e.printStackTrace();
}
return secretKey;
}
/**
* 生成 SecretKey 无salt
*
* @param secret
* @return
*/
private static SecretKey generateSecretKey(String secret) {
return new SecretKeySpec(secret.getBytes(), "AES");
}
/**
* AES256加密
*
* @param content
* @param secretKey
* @return
*/
private static byte[] encryptAES(byte[] content, SecretKey secretKey) {
byte[] str = null;
try {
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.ENCRYPT_MODE, secretKey, new IvParameterSpec(iv));
str = cipher.doFinal(content);
} catch (Exception e) {
e.printStackTrace();
}
return str;
}
/**
* AES256解密
*
* @param bytes
* @param secretKey
* @return
*/
private static byte[] decryptAES(byte[] bytes, SecretKey secretKey) {
byte[] decryptStr = null;
try {
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(iv));
decryptStr = cipher.doFinal(bytes);
} catch (Exception e) {
e.printStackTrace();
}
return decryptStr;
}
/**
* BASE64加密
*
* @param content
* @param secretKey
* @return
*/
private static String encrypt(String content, SecretKey secretKey) throws UnsupportedEncodingException {
byte[] bytes = content.getBytes(UTF_8);
return Base64.encodeBase64String(encryptAES(bytes, secretKey));
}
/**
* BASE64加密
*
* @param content
* @return
*/
public static String encrypt(String content, String key) throws UnsupportedEncodingException {
SecretKey secretKey = generateSecretKey(key);
byte[] bytes = content.getBytes(UTF_8);
return Base64.encodeBase64String(encryptAES(bytes, secretKey));
}
/**
* BASE64解密
*
* @param content
* @param secretKey
* @return
*/
private static String decrypt(String content, SecretKey secretKey) throws UnsupportedEncodingException {
return new String(decryptAES(Base64.decodeBase64(content), secretKey), UTF_8);
}
/**
* BASE64解密
*
* @param content
* @return
*/
public static String decrypt(String content, String key) throws UnsupportedEncodingException {
SecretKey secretKey = generateSecretKey(key);
return new String(decryptAES(Base64.decodeBase64(content), secretKey), UTF_8);
}
public static void main(String[] args) {
try {
// generate secret key
SecretKey secretKey = generateSecretKey("123456", "1234576");
String context = "[{1122334455}]";
String s = encrypt(context, secretKey);
System.out.println(s);
String s1 = decrypt(s, secretKey);
System.out.println(s1);
} catch (Exception e) {
e.printStackTrace();
}
}
}
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
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
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
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
# 3.2 Filter拦截
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.extra.servlet.ServletUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.filter.GenericFilterBean;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
@WebFilter(urlPatterns = "/api/*", filterName = "apiFilter")
public class ApiFilter extends GenericFilterBean {
/**
配置类信息
@Value("${aes.key}")
private String key; //自定义key 分配给前端
@Value("${aes.open}")
private Boolean open; // 是否打开加密
**/
@Autowired
private AesConfig aesConfig;
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
ApiWrapper requestWrapper = null;
if (request instanceof HttpServletRequest) {
HttpServletRequest httpServletRequest = (HttpServletRequest) request;
HttpServletResponse httpServletResponse = (HttpServletResponse) response;
if (!loginFilter(httpServletRequest, httpServletResponse)) {
return;
}
//是否开启加密
if (!aesConfig.getOpen()) {
chain.doFilter(request, response);
return;
}
//只对POST请求做加解密 和前端约定
if (ServletUtil.METHOD_POST.equals(httpServletRequest.getMethod())) {
try {
requestWrapper = new ApiWrapper((HttpServletRequest) request, aesConfig.getKey());
} catch (Exception e) {
throwFilterError(httpServletResponse, HttpStatus.PARAM_ERROR);
return;
}
}
}
chain.doFilter(requestWrapper, response);
}
/**
* 参数解密失败
*
* @param response
*/
private void throwFilterError(HttpServletResponse response, HttpStatus status) throws IOException {
Result result = new Result(status);
response.setCharacterEncoding(CharsetUtil.UTF_8);
response.setContentType(MediaType.APPLICATION_JSON_VALUE);
response.setStatus(200);
PrintWriter printWriter = response.getWriter();
printWriter.append(Json.toJsonString(result));
}
/**
* 登录拦截
*
* @param request
*/
private Boolean loginFilter(HttpServletRequest request, HttpServletResponse response) throws IOException {
//不拦截/order/share
if (request.getRequestURI().contains("share")) {
return true;
}
//分享登录校验
if (request.getRequestURI().contains("inviteDeclaration")
|| request.getRequestURI().contains("checkDeclaration")
|| request.getRequestURI().contains("groupDeclaration")) {
String token = request.getHeader("token");
Object userInfo = RedisUtil.get(RedisKey.API_SHARE_TOKEN + token);
if (ObjectUtil.isNull(userInfo)) {
throwFilterError(response, HttpStatus.LINK_EXPIRED);
return false;
}
ApiUserUtil.putUser(Json.parseObject(userInfo.toString(), ApiUser.class));
} else {
//普通登录校验
if (!request.getRequestURI().contains("login") && !request.getRequestURI().contains("gestureLogin")) {
String token = request.getHeader("token");
Object userInfo = RedisUtil.get(RedisKey.API_TOKEN + token);
if (ObjectUtil.isNull(userInfo)) {
throwFilterError(response, HttpStatus.UNAUTHORIZED);
return false;
}
RedisUtil.set(RedisKey.API_TOKEN + token, userInfo, 7 * 24 * 24 * 60);
ApiUserUtil.putUser(Json.parseObject(userInfo.toString(), ApiUser.class));
}
}
return true;
}
}
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
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
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
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
# 3.3自定义Filter拦截类
import org.apache.commons.io.IOUtils;
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
public class ApiWrapper extends HttpServletRequestWrapper {
private byte[] requestBody = null;//用于将流保存下来
public ApiWrapper(HttpServletRequest request, String key) throws IOException {
super(request);
requestBody = IOUtils.toByteArray(request.getInputStream());
String salt = request.getHeader("timestamp");
//解密参数
requestBody = AESEncrypt.decrypt(new String(requestBody, StandardCharsets.UTF_8), key, salt).getBytes(StandardCharsets.UTF_8);
}
@Override
public ServletInputStream getInputStream() {
// 返回的是我们处理之后的数据
final ByteArrayInputStream bais = new ByteArrayInputStream(requestBody);
return new ServletInputStream() {
@Override
public int read() {
return bais.read(); // 读取 requestBody 中的数据
}
@Override
public boolean isFinished() {
return false;
}
@Override
public boolean isReady() {
return false;
}
@Override
public void setReadListener(ReadListener readListener) {
}
};
}
}
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
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
# 3.4返回参数AOP方式加密
也可以使用ResponseBodyAdvice方式
import cn.hutool.core.util.ObjectUtil;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
@Component
@Aspect
@Slf4j
public class AESAspect {
@Autowired
private AesConfig aesConfig;
private final HttpServletRequest request;
public AESAspect(HttpServletRequest request) {
this.request = request;
}
@Pointcut(value = "execution(* com.felix.api.controller..*.*(..))")
public void encrypt() {
}
/**
* 参数加解密
*
* @param joinPoint
*/
@Around(value = "encrypt()")
public Result encryptParam(ProceedingJoinPoint joinPoint) {
try {
String salt = request.getHeader("timestamp");
Result result = (Result) joinPoint.proceed();
Object data = result.getData();
if (ObjectUtil.isNull(data)) {
return result;
}
if (!aesConfig.getOpen()) {
return result;
}
result.setData(AESEncrypt.encrypt(Json.toJsonString(data), aesConfig.getKey(), salt));
return result;
} catch (ServiceException e) {
throw new ServiceException(e.getMessage());
} catch (Throwable e) {
log.info(e.getMessage());
throw new ServiceException("参数转换失败");
}
}
}
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
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
上次更新: 2026/3/11 21:47:04