SpringBoot + vue前后端数据传输加密
# 前言
在前后端交互时,常常采取http方式进行传输,而明文传输通常会被网络抓包、反编译等手段得到http通讯接地址和参数等。
为了确保信息的安全,在生产中使用了很多种加密手段。
# RAS 非对称加密
RSA也就是非对称加密算法,是使用不同密钥进行加密和解密的算法,也称为公私钥加密。公钥和私钥是同时生成的,公钥用来加密,私钥用来解密,加解密的密钥是成对出现。但是不推荐用RSA加密请求报文,因为RSA加密后的报文会很长,经常会出现超过请求体长度限制。优点是安全性高,缺点是RSA的加密效率低。
该加密方式存在缺点,明文的长度不能超过 117 bytes,如果超长,就会产生异常
javax.crypto.IllegalBlockSizeException: Data must not be longer than 117 bytes
at com.sun.crypto.provider.RSACipher.doFinal(RSACipher.java:346)
at com.sun.crypto.provider.RSACipher.engineDoFinal(RSACipher.java:391)
at javax.crypto.Cipher.doFinal(Cipher.java:2168)
at com.xsaas.hr.stockapp.base.utils.RsaUtils.encrypt(RsaUtils.java:137)
at com.xsaas.RasTest.testRas(RasTest.java:122)
1
2
3
4
5
6
2
3
4
5
6
可以通过分段加密的方式进行处理此种问题。但是 JS 前端代码中分段加密存在缺陷,偶尔会有数据无法解密的情况,分段加解密也存在代码繁琐等问题(常规加密方式是直接调用库,这里在调用库的基础上,还需要加工代码,可能这也是导致解密失败的原因)
# 代码实现
# Java代码
# 生成密钥
/**
* RAS非对称加密,随机生成密钥对
*
* @return 密钥对
*/
public static Map<String, String> genKeyPair() {
// KeyPairGenerator类用于生成公钥和私钥对,基于RSA算法生成对象
KeyPairGenerator keyPairGen = null;
try {
keyPairGen = KeyPairGenerator.getInstance("RSA");
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
// 初始化密钥对生成器,密钥大小为96-1024位
assert keyPairGen != null;
keyPairGen.initialize(1024, new SecureRandom());
// 生成一个密钥对,保存在keyPair中
KeyPair keyPair = keyPairGen.generateKeyPair();
RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate(); // 得到私钥
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic(); // 得到公钥
// 将公钥和私钥保存到Map
Map<String, String> res = new HashMap<String, String>(2) {{
put(AdminConstant.PUBLIC_KEY, new String(Base64.encodeBase64(publicKey.getEncoded())));
put(AdminConstant.PRIVATE_KEY, new String(Base64.encodeBase64((privateKey.getEncoded()))));
}};
return res;
}
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
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
# 公钥加密
/**
* RAS非对称加密: 公钥加密
*
* @param str 加密字符串
* @param publicKey 公钥
* @return 密文
*/
public static String encrypt(String str, String publicKey) {
//base64编码的公钥
byte[] decoded = Base64.decodeBase64(publicKey);
RSAPublicKey pubKey;
String outStr = null;
try {
pubKey = (RSAPublicKey) KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(decoded));
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, pubKey);
outStr = Base64.encodeBase64String(cipher.doFinal(str.getBytes(StandardCharsets.UTF_8)));
} catch (InvalidKeySpecException | BadPaddingException | IllegalBlockSizeException | InvalidKeyException | NoSuchPaddingException | NoSuchAlgorithmException e) {
e.printStackTrace();
}
//RSA加密
return outStr;
}
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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# 私钥解密
/**
* RSA私钥解密
*
* @param str 加密字符串
* @param privateKey 私钥
* @return 铭文
*/
public static String decrypt(String str, String privateKey) {
//64位解码加密后的字符串
byte[] inputByte = Base64.decodeBase64(str.getBytes(StandardCharsets.UTF_8));
//base64编码的私钥
byte[] decoded = Base64.decodeBase64(privateKey);
RSAPrivateKey priKey;
//RSA解密
Cipher cipher;
String outStr = null;
try {
priKey = (RSAPrivateKey) KeyFactory.getInstance("RSA").generatePrivate(new PKCS8EncodedKeySpec(decoded));
cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, priKey);
outStr = new String(cipher.doFinal(inputByte));
} catch (InvalidKeySpecException | NoSuchAlgorithmException | NoSuchPaddingException | BadPaddingException | IllegalBlockSizeException | InvalidKeyException e) {
e.printStackTrace();
}
return outStr;
}
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
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
# 测试类
@Test
public void testRasDemo() {
JSONObject object = new JSONObject();
object.put("name", "我是子龙");
object.put("id", "123456");
object.put("age", 20);
// 随机获取秘钥对
Map<String, String> keyPair = RsaUtils.genKeyPair();
// 秘钥
String privateKey = keyPair.get(AdminConstant.PUBLIC_KEY);
// 公钥
String publicKey = keyPair.get(AdminConstant.PUBLIC_KEY);
// 加密后的数据
String encryptData = null;
try {
// 使用公钥加密
encryptData = RsaUtils.encrypt(JSON.toJSONString(object), publicKey);
} catch (Exception e) {
log.error("加密失败", e);
}
// 解密后的数据
String decryptData = null;
try {
// 使用私钥解密
decryptData = RsaUtils.decrypt(encryptData, privateKey);
} catch (Exception e) {
e.printStackTrace();
}
log.info("原始明文数据={}", object.toJSONString());
log.info("公钥={}", publicKey);
log.info("私钥={}", privateKey);
log.info("加密后的数据={}", encryptData);
log.info("解密后的数据={}", decryptData);
}
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
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
# VUE代码
# 引入加密库
npm i jsencrypt
import JSEncrypt from 'jsencrypt'
1
2
2
# 加密
const encrypt = new JSEncrypt();
encrypt.setPublicKey('你的公钥');
var encryptData = encrypt.encrypt(‘你的数据’);// 加密后的字符串
1
2
3
2
3
# 解密
const decrypt =new JSEncrypt()
decrypt.setPrivateKey(privateKey)
var decryptData = decrypt.decrypt(msg)
1
2
3
2
3
# 测试类
const privateKey = 'xxxxxx'
const msg = 'xxxxxxxxxx'
const encrypt = new JSEncrypt()
encrypt.setPrivateKey(privateKey)
console.log('加密前的数据=' + msg)
console.log('解密后的数据=' + encrypt.decrypt(msg))
1
2
3
4
5
6
7
2
3
4
5
6
7
# 总结
在实际生产环境中,公钥和私钥最好通过后端接口去获取,加上token验证等方式,这样保证安全性,考虑到RAS加密解密的缺点,可以使用RAS+AES结合使用,后续写一篇文章阐述下用法。
上次更新: 2024/07/29, 22:01:05