AES算法介绍

  • 对称加密算法 AES,密码学中的高级加密标准(Advanced Encryption Standard,AES),又称高级加密标准Rijndael加密法,是美国联邦政府采用的一种区块加密标准。
  • 这个标准用来替代原先的DES, 已经被多方分析且广为全世界所使用。经过五年的甄选流程,高级加密标准由美国国家标准与技术研究院(NIST)于2001年11月26日发布于FIPS PUB 197,并在2002年5月26日成为有效的标准。2006年,高级加密标准已然成为对称密钥加密中最流行的算法之一。

加密机制图示

加密机制实现

关于获取设备EMEI

需要文件: jquery.min.js、immersed.js、common.js
获取EMEI码:plus.device.imei

关于前台JS使用AES算法加密

需要文件:(jquery.min.js)aes.js、pad-iso10126-min.js //yptoJS加密库

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
//加密解密代码
var aesKey=“自定义”;//密钥 EMEI
var ivStr=“经某种算法加密的EMEI”;//向量(16位)
/**
* 加密数据
* @param {type} data 待加密的字符串
* @param {type} keyStr 密钥
* @param {type} ivStr 向量
* @returns {unresolved} 加密后的数据
*/
var aesEncrypt = function(data, keyStr, ivStr) {
var sendData = CryptoJS.enc.Utf8.parse(data);
var key = CryptoJS.enc.Utf8.parse(keyStr);
var iv = CryptoJS.enc.Utf8.parse(ivStr);
var encrypted = CryptoJS.AES.encrypt(sendData, key,{iv:iv,mode:CryptoJS.mode.CBC,padding:CryptoJS.pad.Iso10126});
return CryptoJS.enc.Base64.stringify(encrypted.ciphertext);
};
/**
* 解密数据
* @param {type} data BASE64的数据
* @param {type} keyStr 解密密钥
* @param {type} ivStr 向量
* @returns {undefined} 解密后的数据
*/
var aesDecrypt = function(data, keyStr, ivStr) {
var key = CryptoJS.enc.Utf8.parse(keyStr);
var iv = CryptoJS.enc.Utf8.parse(ivStr);
//解密的是基于BASE64的数据,此处data是BASE64数据
var decrypted = CryptoJS.AES.decrypt(data, key, {iv: iv, mode: CryptoJS.mode.CBC, padding:CryptoJS.pad.Iso10126});
return decrypted.toString(CryptoJS.enc.Utf8);
};

关于后台JAVA使用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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
public class Dou_AESUtil {
// SecretKey 负责保存对称密钥的容器
private SecretKeySpec skeySpec;
// Cipher负责完成加密或解密工作
private Cipher c;
// 该字节数组负责保存加密的结果
private byte[] cipherByte;
// 向量iv,用于增加加密算法强度
private IvParameterSpec ivps;

@SuppressWarnings("unused")
private Dou_AESUtil() {
}

/**
* privatekey与iv均为64bit
*/
public Dou_AESUtil(byte[] privatekey, byte[] iv) throws Exception {
Security.addProvider(new com.sun.crypto.provider.SunJCE());
// 使用CBC模式,需要一个向量iv
ivps = new IvParameterSpec(iv);
// 生成密钥
skeySpec = new SecretKeySpec(Base64.decode(Base64.encode(privatekey)),"AES");
// 生成Cipher对象,指定其支持的DES算法
c = Cipher.getInstance("AES/CBC/ISO10126Padding"); // "算法/模式/补码方式"
}

/**
* 对字符串加密
*
* @param str
* @return
* @throws Exception
*/
public byte[] encode(byte[] src) throws Exception {
// 根据密钥,对Cipher对象进行初始化,ENCRYPT_MODE表示加密模式
c.init(Cipher.ENCRYPT_MODE, skeySpec, ivps);
// 加密,结果保存进cipherByte
try {
cipherByte = c.doFinal(src);
} catch (BadPaddingException e) {
System.err.println("error: privatekey is wrong");
}
return cipherByte;
}

/**
* 对字符串解密
*
* @param buff
* @return
* @throws Exception
*/
public byte[] decode(byte[] encodesrc) throws Exception {
// 根据密钥,对Cipher对象进行初始化,DECRYPT_MODE表示加密模式
c.init(Cipher.DECRYPT_MODE, skeySpec, ivps);
cipherByte = c.doFinal(encodesrc);
return cipherByte;
}
}

Action处理类

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
public class wUserAction extends ActionSupport {

private static final long serialVersionUID = 1L;

// 1.前后台通信对象request,response
HttpServletRequest request = ServletActionContext.getRequest();
HttpServletResponse response = ServletActionContext.getResponse();
HttpSession session = request.getSession();// session模拟数据库

// 2.struts配置返回的json串,必须有set方法配套
String json = null;

public String getJson() {
return json;
}

public void setJson(String json) {
this.json = json;
}

/**
* 功能:注册
*
* @return
*/
public String registers() {
try {
// 获取注册传来的注册信息
String registername = new String(request.getParameter("name").getBytes("iso-8859-1"), "utf-8");
String registerpwd = new String(request.getParameter("pwd").getBytes("iso-8859-1"), "utf-8");
String registerimei = new String(request.getParameter("imei").getBytes("iso-8859-1"), "utf-8");

// 模拟存入数据库
session.setAttribute("name", registername);
session.setAttribute("pwd", registerpwd);
session.setAttribute("IMEI", registerimei);

// 存入成功,模拟注册成功
System.out.println("用户名:" + registername + ",密码:" + registerpwd + ",IMEI:" + registerimei + " 注册成功");

this.json = "注册成功!";
return SUCCESS;
} catch (Exception e) {
e.printStackTrace();
return ERROR;
}
}

/**
* 功能:登录
*
* @return
* @throws IOException
*/
public String login() throws IOException {

request.setCharacterEncoding("UTF-8");

try {
// 接受客户端发送的消息(加密状态且已base64编码)
byte[] bytename = request.getParameter("name").getBytes("iso-8859-1");
byte[] bytepwd = request.getParameter("pwd").getBytes("iso-8859-1");
System.out.println("客户端发来的消息(加密状态): name: " + new String(bytename) + " pwd: " + new String(bytepwd));

// base64解码
bytename = Base64.decode(bytename);
bytepwd = Base64.decode(bytepwd);

// 解密消息,解密后为byte类型
String privatekey = (String) session.getAttribute("IMEI");
bytename = new Dou_AESUtil(privatekey.getBytes(), privatekey.replace("0", "1").getBytes()).decode(bytename);
bytepwd = new Dou_AESUtil(privatekey.getBytes(), privatekey.replace("0", "1").getBytes()).decode(bytepwd);

// 将byte类型数据包装成String
String name = new String(bytename, "UTF-8");
String pwd = new String(bytepwd, "UTF-8");

System.out.println("客户端发来的消息(解密状态): name: " + name + " pwd: " + pwd);

// 判断"数据库"的值与用户输入的值是否匹配
if (session.getAttribute("name").equals(name) && session.getAttribute("pwd").equals(pwd)) {
System.out.println("用户名:" + name + ",密码:" + pwd + " 登录成功!");

// 返回消息到客户端,以下是对数据进行加密
byte[] bytes = new Dou_AESUtil(privatekey.getBytes("UTF-8"),privatekey.replace("0", "1").getBytes()).encode(("abcdefghijkmnopqrstuvwxyz1234567890").getBytes("UTF-8"));

// 将数据编码为Base64
String send = Base64.encode(bytes).replace("\r", "").replace("\n", "").replace("\t", "");
System.out.println("服务器发出的消息(加密状态): " + send);

this.json = send;
} else {
System.out.println("用户名:" + name + ",密码:" + pwd + "正确IMEI:" + session.getAttribute("IMEI") + " 登录失败!");

// 返回消息到客户端,以下是对数据进行加密
byte[] bytes = new Dou_AESUtil(privatekey.getBytes("UTF-8"),privatekey.replace("0", "1").getBytes()).encode(("不是本机登录,登录失败!").getBytes());

// 将数据编码为Base64
String send = Base64.encode(bytes).replace("\r", "").replace("\n", "").replace("\t", "");
System.out.println("服务器发出的消息(加密状态): " + send);

this.json = send;
}
return SUCCESS;
} catch (Exception e) {
e.printStackTrace();
}
return SUCCESS;
}
}

注:Java与CryptoJS分别对AES加密模式的支持情况

【Java】


算法/模式/填充 16字节加密后数据长度 加密内容不满16字节加密后长度
AES/CBC/NoPadding 16 不支持
AES/CBC/PKCS5Padding 32 16
AES/CBC/ISO10126Padding 32 16
AES/CFB/NoPadding 16 原始数据长度
AES/CFB/PKCS5Padding 32 16
AES/CFB/ISO10126Padding32 16
AES/ECB/NoPadding 16 不支持 09
AES/ECB/PKCS5Padding 32 16
AES/ECB/ISO10126Padding32 16
AES/OFB/NoPadding 16 原始数据长度
AES/OFB/PKCS5Padding 32 16
AES/OFB/ISO10126Padding32 16
AES/PCBC/NoPadding 16 不支持
AES/PCBC/PKCS5Padding 32 16
AES/PCBC/ISO10126Padding 32 16

【CryptoJS 】


Pkcs7(the default)
Iso97971
AnsiX923
Iso10126
ZeroPadding
NoPadding

还需以后研究的问题

  1. 该加密机制,对于Tomcat服务器来说,仅支持Tomcat7.0以上,而对于其他低级版本会出现服务器给客户端的 加密后的返回消息内容不正确。(PS:该问题相当坑爹,那一夜,我搞到了凌晨3点半才弄明白,原来代码没问题,是服务器的问题。这问题是有多难找啊。)

  2. 对于App访问服务器,会出现返回消息内容个别字符乱码,并且经查证,乱码字符正是正确字符在Ascii码表上的上一个字符或者下一个字符。