JavaWeb实现RSA+AES混合加密
作者:YO_RUI
RSA+AES的混合加密时,AES用于给传输的数据加密,然后通过RSA给AES的秘钥加密,本文就来详细的介绍一下如何实现,具有一定的参考价值,感兴趣的可以了解一下
一、前言
RSA与AES加密的详细介绍这里就不写了,网上很多博客,这里就只是简单说明一下:
- AES:属于对称加密,通过一个公共的秘钥,实现加密解密;
- RSA:非对称加密,需要生成一个公钥、一个私钥,这两个秘钥使用时,一个用来加密时,那么就需要另一个秘钥进行解密,公钥一般提供给客户端。
二、整体构思
RSA+AES的混合加密时,AES用于给传输的数据加密,然后通过RSA给AES的秘钥加密,所以接收到数据后,就需要先解密得到AES的秘钥,然后通过AES秘钥再去解密得到数据。
下面简单说下demo中加密解密的实现过程:
- 前后端各自生成自己的RSA公私密钥(这就必须确保双方的RSA算法要匹配,不然双方就无法正常解密)
- 当访问项目首页时,前端生成RSA秘钥,并存放在window对象中的localStorage
- 页面发起请求获取服务端的RSA公钥,服务端收到请求后生成RSA公司秘钥,并将秘钥放入session,所以每次建立会话连接时都是不一样的秘钥,然后将公钥返回给前端页面
- 页面接收到服务端的RSA公钥后,存入window对象,然后用服务端RSA公钥加密前端的RSA公钥发送给服务端
- 服务端收到前端发过来的请求后,通过自己的私钥解密数据,从而得到前端的公钥,并存入session。
这里面提到的存储秘钥的方式只是在demo中作为演示使用,可以采用更合理、更安全的方式是实现!
这样,前后端都拥有的对方RSA的公钥,后面在同一个会话中具体的请求数据时,每次各自都会生成新的AES秘钥(AES的算法也需要前后端能匹配上),RSA的秘钥则在响应位置去取就可以了。
三、主要代码
1、服务端
两个加密解密工具类,里面部分有使用第三方jar(hutool-all.jar)。
- AESUtil
package com.lr.demo.util; import javax.crypto.Cipher; import javax.crypto.KeyGenerator; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; import java.io.UnsupportedEncodingException; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; import java.util.Arrays; public class AESUtil { private static final String KEY_ALGORITHM = "AES"; private static final String DEFAULT_CIPHER_ALGORITHM = "AES/ECB/PKCS5Padding";//默认的加密算法 public static String getKey(int len){ if(len % 16 != 0){ System.out.println("长度要为16的整数倍"); return null; } char[] chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz".toCharArray(); char[] uuid = new char[len]; if (len > 0) { for (int i = 0; i < len; i++) { int x = (int) (Math.random() * (len - 0 + 1) + 0); uuid[i] = chars[x % chars.length]; } } return new String(uuid); } public static String byteToHexString(byte[] bytes){ StringBuffer sb = new StringBuffer(); for (int i = 0; i < bytes.length; i++) { String strHex=Integer.toHexString(bytes[i]); if(strHex.length() > 3){ sb.append(strHex.substring(6)); } else { if(strHex.length() < 2){ sb.append("0" + strHex); } else { sb.append(strHex); } } } return sb.toString(); } /** * AES 加密操作 * * @param content 待加密内容 * @param key 加密密码 * @return 返回Base64转码后的加密数据 */ public static String encrypt(String content, String key) { try { Cipher cipher = Cipher.getInstance(DEFAULT_CIPHER_ALGORITHM);// 创建密码器 byte[] byteContent = content.getBytes("utf-8"); cipher.init(Cipher.ENCRYPT_MODE, getSecretKey(key));// 初始化为加密模式的密码器 byte[] result = cipher.doFinal(byteContent);// 加密 return org.apache.commons.codec.binary.Base64.encodeBase64String(result);//通过Base64转码返回 } catch (Exception ex) { ex.printStackTrace(); } return null; } /** * AES 解密操作 * * @param content * @param key * @return */ public static String decrypt(String content, String key) { try { //实例化 Cipher cipher = Cipher.getInstance(DEFAULT_CIPHER_ALGORITHM); //使用密钥初始化,设置为解密模式 cipher.init(Cipher.DECRYPT_MODE, getSecretKey(key)); //执行操作 byte[] result = cipher.doFinal(org.apache.commons.codec.binary.Base64.decodeBase64(content)); return new String(result, "utf-8"); } catch (Exception ex) { ex.printStackTrace(); } return null; } private static SecretKeySpec getSecretKey(final String key) throws UnsupportedEncodingException { //返回生成指定算法密钥生成器的 KeyGenerator 对象 KeyGenerator kg = null; try { kg = KeyGenerator.getInstance(KEY_ALGORITHM); //AES 要求密钥长度为 128 kg.init(128, new SecureRandom(key.getBytes())); //生成一个密钥 SecretKey secretKey = kg.generateKey(); return new SecretKeySpec(Arrays.copyOf(key.getBytes("utf-8"), 16), KEY_ALGORITHM);// 转换为AES专用密钥 } catch (NoSuchAlgorithmException ex) { ex.printStackTrace(); } return null; } }
- RSAUtil
package com.lr.demo.util; import cn.hutool.crypto.SecureUtil; import cn.hutool.crypto.asymmetric.KeyType; import cn.hutool.crypto.asymmetric.RSA; import org.springframework.util.Base64Utils; import java.io.UnsupportedEncodingException; import java.security.Key; import java.security.KeyPair; import java.security.PrivateKey; import java.security.PublicKey; import java.util.HashMap; import java.util.Map; public class RSAUtil { public static final String PB_KEY = "pb_key"; public static final String PR_KEY = "pr_key"; public static final String CLI_PB_KEY = "cli_pb_key"; /** * 获取公私秘钥对 * @return */ public static Map<String, Key> getRSAKey(){ KeyPair pair = SecureUtil.generateKeyPair("RSA"); PrivateKey privateKey = pair.getPrivate(); PublicKey publicKey = pair.getPublic(); Map<String, Key> keys = new HashMap<>(); keys.put(PR_KEY,privateKey); keys.put(PB_KEY,publicKey); return keys; } /** * 公钥加密 * @param pbKey * @param content * @return */ public static String encByPbKey(String pbKey,String content){ try { byte[] bytes = Base64Utils.decode(pbKey.getBytes("UTF-8")); RSA rsa = new RSA(null,bytes); byte[] enc = rsa.encrypt(content.getBytes("UTF-8"), KeyType.PublicKey); String s = new String(Base64Utils.encode(enc)); return s; } catch (UnsupportedEncodingException e) { e.printStackTrace(); } return null; } /** * 公钥加密 * @param pbKey * @param content * @return */ public static String encByPbKey(PublicKey pbKey,String content){ try { RSA rsa = new RSA(null,pbKey); byte[] enc = rsa.encrypt(content.getBytes("UTF-8"), KeyType.PublicKey); String s = new String(Base64Utils.encode(enc)); return s; } catch (UnsupportedEncodingException e) { e.printStackTrace(); } return null; } /** * 私钥解密 * @param prKey * @param content * @return */ public static String dencByPrKey(String prKey,String content){ try { byte[] bytes = Base64Utils.decode(prKey.getBytes("UTF-8")); RSA rsa = new RSA(bytes,null); byte[] denc = rsa.decrypt(Base64Utils.decode(content.getBytes("UTF-8")), KeyType.PrivateKey); String s = new String(Base64Utils.encode(denc)); return s; } catch (UnsupportedEncodingException e) { e.printStackTrace(); } return null; } /** * 私钥解密 * @param prKey * @param content * @return */ public static String dencByPrKey(PrivateKey prKey,String content){ try { RSA rsa = new RSA(prKey,null); byte[] denc = rsa.decrypt(Base64Utils.decode(content.getBytes("UTF-8")), KeyType.PrivateKey); String s = new String(denc); return s; } catch (UnsupportedEncodingException e) { e.printStackTrace(); } return null; } }
l两个Controller,一个是初始化是秘钥交换的,一个用于测试
package com.lr.demo.controller; import com.lr.demo.commons.Result; import com.lr.demo.util.RSAUtil; import org.springframework.util.Base64Utils; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.servlet.http.HttpSession; import java.security.Key; import java.security.PrivateKey; import java.util.List; import java.util.Map; @RestController @RequestMapping("secret") public class SecretController { /** * 返回服务端的RSA公钥 * @param session * @return */ @RequestMapping("getKey") public Result getKey(HttpSession session){ Map<String, Key> rsaKey = (Map<String, Key>) session.getAttribute("keys"); if(rsaKey == null){ rsaKey = RSAUtil.getRSAKey(); session.setAttribute("keys",rsaKey); } byte[] encode = Base64Utils.encode(rsaKey.get(RSAUtil.PB_KEY).getEncoded()); return Result.success(new String(encode)); } /** * 分段解密发送过来的客户端RSA公钥 * @param map * @param session * @return */ @RequestMapping("acceptKey") public Result acceptKey(@RequestBody Map<String,Object> map, HttpSession session){ List<String> clientKeys = (List<String>) map.get("clientKey"); System.out.println("clientKey:" + clientKeys); Map<String, Key> rsaKey = (Map<String, Key>) session.getAttribute("keys"); String cli_key = ""; if(clientKeys != null){ for (String item : clientKeys) { cli_key += RSAUtil.dencByPrKey((PrivateKey) rsaKey.get(RSAUtil.PR_KEY), item); } } session.setAttribute(RSAUtil.CLI_PB_KEY,cli_key); System.out.println("解密后客户端公钥:" + cli_key); return Result.success(); } }
package com.lr.demo.controller; import com.alibaba.fastjson.JSON; import com.lr.demo.commons.Constant; import com.lr.demo.commons.Result; import com.lr.demo.util.AESUtil; import com.lr.demo.util.RSAUtil; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpSession; import java.security.Key; import java.security.PrivateKey; import java.util.HashMap; import java.util.Map; @RestController @RequestMapping("sys") public class SystemController { @RequestMapping("login") public Result login(@RequestBody String data, HttpServletRequest request,HttpServletResponse response){ String plaintext = dencrypt(request, data); return Result.success(encrypt("登录成功啦",response)); } private String dencrypt(HttpServletRequest request,String data){ // 从session中获取服务端RSA的私钥 HttpSession session = request.getSession(); Map<String, Key> rsaKey = (Map<String, Key>) session.getAttribute("keys"); HashMap<String,String> hashMap = JSON.parseObject(data, HashMap.class); // 获取客户端发送的加密数据 String enc_data = hashMap.get(Constant.ENCRYPT_DATA); System.out.println("获取请求数据---->:" + enc_data); // 获取发送过来的AES秘钥 String enc_aes_key = request.getHeader(Constant.ENCRYPT_AES_KEY); // 解密AES秘钥 String aes_key = RSAUtil.dencByPrKey((PrivateKey) rsaKey.get(RSAUtil.PR_KEY), enc_aes_key); // AES解密 String plaintext = AESUtil.decrypt(enc_data, aes_key); System.out.println("解密数据---->:" + plaintext); return plaintext; } public Map<String, String> encrypt(String data, HttpServletResponse response){ HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); HttpSession session = request.getSession(); String cliKey = (String) session.getAttribute(RSAUtil.CLI_PB_KEY); // 获取客户端RSA公钥 String aesKey = AESUtil.getKey(16); // 获取AES秘钥 // RSA加密AES秘钥 String encrypt_aes_key = RSAUtil.encByPbKey(cliKey, aesKey); // AES加密返回数据 String encrypt_data = AESUtil.encrypt(data, aesKey); // 添加响应头(AES秘钥) response.addHeader(Constant.ENCRYPT_AES_KEY, encrypt_aes_key); Map<String,String> map = new HashMap<>(); map.put(Constant.ENCRYPT_DATA,encrypt_data); return map; } }
2、前端
前端涉及的文件较多,这里就只展示下页面,其余详细代码可以下载源码后查看。
- aes_v1.0.js:AES加解密
- rsa.js、crypto-js.js:RSA加解密
- demo.js:封装的函数
index.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>$Title$</title> <script src="static/js/aes_v1.0.js"></script> <script src="static/js/rsa.js"></script> <script src="static/js/crypto-js.js"></script> <script src="static/js/demo.js"></script> <script src="static/js/jquery-3.5.1.js"></script> </head> <body> <form action="<%=request.getContextPath()%>/sys/login" id="loginForm"> <input type="text" name="username" value=""> <input type="password" name="password" value=""> <input type="button" value="登录" id="loginBtn"> </form> <script> // 首页加载时,秘钥生成和交换 getRsaKeys(f) </script> <script> function getFormJson(formJqueryObj) { var o = {}; var a = formJqueryObj.serializeArray(); $.each(a, function () { if (o[this.name]) { if (!o[this.name].push) { o[this.name] = [o[this.name]]; } o[this.name].push(this.value || ''); } else { o[this.name] = this.value || ''; } }); return o; } $('#loginBtn').click(function () { var json = getFormJson($('#loginForm')) // demo.js封装的函数 request(json,'<%=request.getContextPath()%>/sys/login',function (res) { console.log(res) }) }) </script> </body> </html>
四、测试
启动项目,打开首页:
服务端日志:
当交换完秘钥后,进行登录测试:
服务端就收时的日志输出:
响应后页面的输出:
以上就是一个简单的demo,源码点击下载。
到此这篇关于JavaWeb实现RSA+AES混合加密的文章就介绍到这了,更多相关JavaWe RSA+AES混合加密内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!