项目已经接近尾声,一个Web网页聊天室P-LinkC。出于信息安全的考虑,准备为聊天室添加一套信息加密技术对聊天信息进行加密处理。这里我选用AES对称加密技术。

What is AES?

AES加密又称“高级加密标准”,在密码学中又称Rijndael加密法,是美国联邦政府采用的一种区块加密标准。这个标准被用来替代原先的DES,已经被多方分析且广为全世界所使用。 — 维基百科

简单了解AES加密

对称加密与非对称加密

  • 对称加密:用户双方使用同一套密钥加解密

对称加密.png

  • 非对称加密:用户双方在信息交互开始前得到对方公钥,发送密文用对方公钥加密,对方用自己的私钥解密

非对称加密.png

AES算法是一种对称加密算法

AES加密的特点

  • 密钥
  • 填充
  • 模式

AES的密钥

密钥是AES算法实现加密和解密的根本。对称加密算法之所以对称,是因为这类算法对明文的加密和解密需要使用同一个密钥。
AES支持三种长度的密钥:128位,192位,256位。其中:

  • 128位密钥最高效
  • 256位密钥最安全

填充加密

AES算法在对明文加密的时候,并不是把整个明文一股脑加密成一整段密文,而是把明文拆分成一个个独立的明文块,每一个明文块长度128bit进行独立加密再拼接。如果一个明文块长度不够128bit将会被填充至128bit。

工作模式

AES的工作模式,体现在把明文块加密成密文块的处理过程中。AES加密算法提供了五种不同的工作模式:ECB、CBC、CTR、CFB、OFB。加密解密的模式必须一致!

AES加密在WebSocket上的实现

后端代码 - AesUtil类

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

/**
* AES加/解密算法工具类
*/
public class AesUtil {
/**
* 加密算法AES
*/
private static final String KEY_ALGORITHM = "AES";

/**
* key的长度,Wrong key size: must be equal to 128, 192 or 256
* 传入时需要16、24、36
*/
private static final Integer KEY_LENGTH = 16 * 8;

/**
* 算法名称/加密模式/数据填充方式
* 默认:AES/ECB/PKCS5Padding
*/
private static final String ALGORITHMS = "AES/ECB/PKCS5Padding";

/**
* 后端AES的key,由静态代码块赋值
*/
public static String key;

static {
key = getKey();
}

/**
* 获取key
*/
public static String getKey() {
StringBuilder uid = new StringBuilder();
//产生16位的强随机数
Random rd = new SecureRandom();
for (int i = 0; i < KEY_LENGTH / 8; i++) {
//产生0-2的3位随机数
int type = rd.nextInt(3);
switch (type) {
case 0:
//0-9的随机数
uid.append(rd.nextInt(10));
break;
case 1:
//ASCII在65-90之间为大写,获取大写随机
uid.append((char) (rd.nextInt(25) + 65));
break;
case 2:
//ASCII在97-122之间为小写,获取小写随机
uid.append((char) (rd.nextInt(25) + 97));
break;
default:
break;
}
}
return uid.toString();
}

/**
* 加密
*
* @param content 加密的字符串
* @param encryptKey key值
*/
public static String encrypt(String content, String encryptKey) throws Exception {
//设置Cipher对象
Cipher cipher = Cipher.getInstance(ALGORITHMS,new BouncyCastleProvider());
cipher.init(Cipher.ENCRYPT_MODE, new SecretKeySpec(encryptKey.getBytes(), KEY_ALGORITHM));

//调用doFinal
byte[] b = cipher.doFinal(content.getBytes(StandardCharsets.UTF_8));

// 转base64
return Base64.encodeBase64String(b);

}

/**
* 解密
*
* @param encryptStr 解密的字符串
* @param decryptKey 解密的key值
*/
public static String decrypt(String encryptStr, String decryptKey) throws Exception {
//base64格式的key字符串转byte
byte[] decodeBase64 = Base64.decodeBase64(encryptStr);

//设置Cipher对象
Cipher cipher = Cipher.getInstance(ALGORITHMS,new BouncyCastleProvider());
cipher.init(Cipher.DECRYPT_MODE, new SecretKeySpec(decryptKey.getBytes(), KEY_ALGORITHM));

//调用doFinal解密
byte[] decryptBytes = cipher.doFinal(decodeBase64);
return new String(decryptBytes);
}

}

后端AES工具类简单测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public static void main(String[] args) {
//16位
String key = "MIGfMA0GCSqGSIb3";

//字符串
String str = "唱、跳、Rap、篮球";
try {
//加密
String encrypt = AesUtil.encrypt(str, key);
//解密
String decrypt = AesUtil.decrypt(encrypt, key);

System.out.println("加密前:" + str);
System.out.println("加密后:" + encrypt);
System.out.println("解密后:" + decrypt);
} catch (Exception e) {
e.printStackTrace();
}
}

测试结果

后端AES加密测试.png

前端AES算法实现

CryptoJS

AES我们采用CryptoJS,是一个标准和安全加密算法的JavaScript库,它的AES加密支持AES-128、AES-192和AES-256。下载或查看详情介绍请戳官网地址

GitHub地址:https://github.com/brix/crypto-js

官网地址:https://code.google.com/archive/p/crypto-js/

整合AES算法

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
/**
* 简单封装一下
*/
var aesUtil = {

//获取key,
genKey : function (length = 16) {
let random = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
let str = "";
for (let i = 0; i < length; i++) {
str = str + random.charAt(Math.random() * random.length)
}
return str;
},

//加密
encrypt : function (plaintext,key) {
if (plaintext instanceof Object) {
//JSON.stringify
plaintext = JSON.stringify(plaintext)
}
let encrypted = CryptoJS.AES.encrypt(CryptoJS.enc.Utf8.parse(plaintext), CryptoJS.enc.Utf8.parse(key), {mode:CryptoJS.mode.ECB,padding: CryptoJS.pad.Pkcs7});
return encrypted.toString();
},

//解密
decrypt : function (ciphertext,key) {
let decrypt = CryptoJS.AES.decrypt(ciphertext, CryptoJS.enc.Utf8.parse(key), {mode:CryptoJS.mode.ECB,padding: CryptoJS.pad.Pkcs7});
let decString = CryptoJS.enc.Utf8.stringify(decrypt).toString();
if(decString.charAt(0) === "{" || decString.charAt(0) === "[" ){
//JSON.parse
decString = JSON.parse(decString);
}
return decString;
}
};

aesUtil

前端AES工具类简单测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<script>
//字符串
let text = "唱、跳、Rap、篮球";
//key
let genKey = aesUtil.genKey();
//key加密
let ciphertext = aesUtil.encrypt(text,genKey);
//key解密
let plaintext = aesUtil.decrypt(ciphertext,genKey);

console.log("key:");console.log(genKey);
console.log("加密前:");console.log(text);
console.log("key加密后:" + ciphertext);
console.log("key解密后:");console.log(plaintext);
</script>

测试结果

前端AES测试.png

前后端联调测试

主要目的是对用户聊天信息进行加密,故在前端打包加密用户发送的聊天信息。

前端部分

1
2
3
4
5
6
7
8
9
10
11
12
 let aesKey = aesUtil.genKey();
let data = {
data:aesUtil.encrypt(JSON.stringify({
message : {
content : message,
from : '${userid}',
to : to, //接收人,如果没有则置空,如果有多个接收人则用,分隔
time : getDateFull()
},
type : "message"
}),aesKey),aesKey
}

WebSocket服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
String decrypt ="";
try {
//jackson
ObjectMapper mapper = new ObjectMapper();
//jackson 序列化和反序列化 date处理
mapper.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
//JSON字符串转 HashMap
HashMap map = mapper.readValue(_message, HashMap.class);

//先解密
String data = (String) map.get("data");
String aesKey = (String) map.get("aesKey");
System.out.println("加密后:"+data);
System.out.println("密钥:"+aesKey);

//AES解密得到明文data数据
decrypt = AesUtil.decrypt(data, aesKey);
System.out.println("解密后:"+decrypt);


} catch (Exception e) {
e.printStackTrace();
}

整体整合测试

  • 前端发送图文信息

3447d9660224accbc572c8e3cdde114.png

  • 后端接收到的加密信息

加密信息.png
加密信息2.png

  • 解密后明文

8fd75e3aa39119d7e576ddd1d5175d0.png

1
2
3
4
5
加密后:JSV0//HjCAhPzTf/XQtau0/5w6ucq++gVcSrODlUfQdHGvgyV9oilqlyeB/JxVa/cCCxg6ML6XQ8+TYVwaF1qYHWfWc8BWRT7y2MovFGpgu+t+zuUCNIk/ExErp8ToACSXA1P+aF0NbK9TFn8aXcu9NGJl988A9Yn/74l+ia9llcQjA2ocNVkXn1k/tRPEHJs7yslO6VtY5aql774MulL03Sq99XH1mWo52y2JygMS3RXf23G6lKZxzjhjbY5xi59pUt4wk1SWMeqUdyb7bGlEq+aUvnnKqn/C3SRDbmrjg=

密钥:juLvarG4elTqa8zc

解密后:{"message":{"content":"<p>鸡你太美~</p><p><img src=\"/upload/6edbabcc-ab55-4195-a730-376b62dc50f4.jpeg\" style=\"max-width:100%;\"></p>","from":"admin","to":"","time":"2019-09-02 11:25:38"},"type":"message"}
  • 回显前端

9b353c32e1c5ee446eb557deeb85fa2.png

2019年9月2日 WebSocket结合AES加密算法实现加密聊天完成

参考

高级加密标准 -维基百科

漫画解读:什么是AES算法

一文读懂对称加密算法、非对称加密算法和 Hash 算法

WebSocket数据加密