对称加密算法 AES联合设备IMEI码设计的加密机制
1.AES算法介绍
* 对称加密算法 AES,密码学中的高级加密标准(Advanced Encryption Standard,AES),又称高级加密标准Rijndael加密法,是美国联邦政府采用的一种区块加密标准。
*这个标准用来替代原先的DES,已经被多方分析且广为全世界所使用。经过五年的甄选流程,高级加密标准由美国国家标准与 技术研究院(NIST)于2001年11月26日发布于FIPS PUB197,并在2002年5月26日成为有效的标准。2006年,高级加密标准已然成为对称密钥加密中最流行的算法之一。
2.加密机制图示
3.加密机制实现
一,关于获取设备EMEI
需要文件:jquery.min.js、immersed.js、common.js
plus.device.imei //获取EMEI码
二,关于前台JS使用AES算法加密
需要文件: (jquery.min.js)aes.js、pad-iso10126-min.js 源自CryptoJS加密库
//加密解密代码 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算法加密
加密工具类:
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 NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException { 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 InvalidKeyException * @throws IllegalBlockSizeException * @throws BadPaddingException * @throws InvalidAlgorithmParameterException */ public byte[] encode(byte[] src) throws InvalidKeyException, IllegalBlockSizeException, InvalidAlgorithmParameterException { // 根据密钥,对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 InvalidKeyException * @throws IllegalBlockSizeException * @throws BadPaddingException * @throws InvalidAlgorithmParameterException */ public byte[] decode(byte[] encodesrc) throws InvalidKeyException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException { // 根据密钥,对Cipher对象进行初始化,DECRYPT_MODE表示加密模式 c.init(Cipher.DECRYPT_MODE, skeySpec, ivps); cipherByte = c.doFinal(encodesrc); return cipherByte; } }
Action处理类:
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 (InvalidKeyException e) { e.printStackTrace(); } catch (IllegalBlockSizeException e) { e.printStackTrace(); } catch (BadPaddingException e) { e.printStackTrace(); } catch (InvalidAlgorithmParameterException e) { e.printStackTrace(); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } catch (NoSuchPaddingException e) { e.printStackTrace(); } catch (Base64DecodingException 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/ISO10126Padding 32 16* AES/ECB/NoPadding 16 不支持 09* AES/ECB/PKCS5Padding 32 16* AES/ECB/ISO10126Padding 32 16* AES/OFB/NoPadding 16 原始数据长度* AES/OFB/PKCS5Padding 32 16* AES/OFB/ISO10126Padding 32 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码表上的上一个字符或者下一个字符。