JavaWeb實現(xiàn)RSA+AES混合加密
一、前言
RSA與AES加密的詳細(xì)介紹這里就不寫了,網(wǎng)上很多博客,這里就只是簡單說明一下:
- AES:屬于對稱加密,通過一個公共的秘鑰,實現(xiàn)加密解密;
- RSA:非對稱加密,需要生成一個公鑰、一個私鑰,這兩個秘鑰使用時,一個用來加密時,那么就需要另一個秘鑰進(jìn)行解密,公鑰一般提供給客戶端。
二、整體構(gòu)思
RSA+AES的混合加密時,AES用于給傳輸?shù)臄?shù)據(jù)加密,然后通過RSA給AES的秘鑰加密,所以接收到數(shù)據(jù)后,就需要先解密得到AES的秘鑰,然后通過AES秘鑰再去解密得到數(shù)據(jù)。
下面簡單說下demo中加密解密的實現(xiàn)過程:
- 前后端各自生成自己的RSA公私密鑰(這就必須確保雙方的RSA算法要匹配,不然雙方就無法正常解密)
- 當(dāng)訪問項目首頁時,前端生成RSA秘鑰,并存放在window對象中的localStorage
- 頁面發(fā)起請求獲取服務(wù)端的RSA公鑰,服務(wù)端收到請求后生成RSA公司秘鑰,并將秘鑰放入session,所以每次建立會話連接時都是不一樣的秘鑰,然后將公鑰返回給前端頁面
- 頁面接收到服務(wù)端的RSA公鑰后,存入window對象,然后用服務(wù)端RSA公鑰加密前端的RSA公鑰發(fā)送給服務(wù)端
- 服務(wù)端收到前端發(fā)過來的請求后,通過自己的私鑰解密數(shù)據(jù),從而得到前端的公鑰,并存入session。
這里面提到的存儲秘鑰的方式只是在demo中作為演示使用,可以采用更合理、更安全的方式是實現(xiàn)!
這樣,前后端都擁有的對方RSA的公鑰,后面在同一個會話中具體的請求數(shù)據(jù)時,每次各自都會生成新的AES秘鑰(AES的算法也需要前后端能匹配上),RSA的秘鑰則在響應(yīng)位置去取就可以了。
三、主要代碼
1、服務(wù)端
兩個加密解密工具類,里面部分有使用第三方j(luò)ar(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";//默認(rèn)的加密算法 public static String getKey(int len){ if(len % 16 != 0){ System.out.println("長度要為16的整數(shù)倍"); 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 待加密內(nèi)容 * @param key 加密密碼 * @return 返回Base64轉(zhuǎn)碼后的加密數(shù)據(jù) */ public static String encrypt(String content, String key) { try { Cipher cipher = Cipher.getInstance(DEFAULT_CIPHER_ALGORITHM);// 創(chuàng)建密碼器 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轉(zhuǎn)碼返回 } 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); //使用密鑰初始化,設(shè)置為解密模式 cipher.init(Cipher.DECRYPT_MODE, getSecretKey(key)); //執(zhí)行操作 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);// 轉(zhuǎn)換為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 { /** * 返回服務(wù)端的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)); } /** * 分段解密發(fā)送過來的客戶端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中獲取服務(wù)端RSA的私鑰 HttpSession session = request.getSession(); Map<String, Key> rsaKey = (Map<String, Key>) session.getAttribute("keys"); HashMap<String,String> hashMap = JSON.parseObject(data, HashMap.class); // 獲取客戶端發(fā)送的加密數(shù)據(jù) String enc_data = hashMap.get(Constant.ENCRYPT_DATA); System.out.println("獲取請求數(shù)據(jù)---->:" + enc_data); // 獲取發(fā)送過來的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("解密數(shù)據(jù)---->:" + 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加密返回數(shù)據(jù) String encrypt_data = AESUtil.encrypt(data, aesKey); // 添加響應(yīng)頭(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、前端
前端涉及的文件較多,這里就只展示下頁面,其余詳細(xì)代碼可以下載源碼后查看。
- aes_v1.0.js:AES加解密
- rsa.js、crypto-js.js:RSA加解密
- demo.js:封裝的函數(shù)
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封裝的函數(shù) request(json,'<%=request.getContextPath()%>/sys/login',function (res) { console.log(res) }) }) </script> </body> </html>
四、測試
啟動項目,打開首頁:
服務(wù)端日志:
當(dāng)交換完秘鑰后,進(jìn)行登錄測試:
服務(wù)端就收時的日志輸出:
響應(yīng)后頁面的輸出:
以上就是一個簡單的demo,源碼點擊下載。
到此這篇關(guān)于JavaWeb實現(xiàn)RSA+AES混合加密的文章就介紹到這了,更多相關(guān)JavaWe RSA+AES混合加密內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java Map 通過 key 或者 value 過濾的實例代碼
這篇文章主要介紹了Java Map 通過 key 或者 value 過濾的實例代碼,非常不錯,具有一定的參考借鑒價值,需要的朋友可以參考下2018-06-06springboot集成redis并使用redis生成全局唯一索引ID
本文主要介紹了springboot集成redis并使用redis生成全局唯一索引ID,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-03-03Mybatis報錯日志BindingException的解決
本文主要介紹了Mybatis報錯日志BindingException的解決,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-07-07MybatisPlus實現(xiàn)數(shù)據(jù)權(quán)限隔離的示例詳解
Mybatis Plus對Mybatis做了無侵入的增強,非常的好用,今天就給大家介紹它的其中一個實用功能:數(shù)據(jù)權(quán)限插件,感興趣的可以跟隨小編一起了解下2024-04-04Java中從Integer到Date的轉(zhuǎn)換方法
這篇文章主要介紹了Java中integer怎么轉(zhuǎn)換date,在Java中,如果我們有一個Integer類型的數(shù)據(jù),想要將其轉(zhuǎn)換為Date類型,本文給大家介紹了實現(xiàn)方法,并通過代碼示例講解的非常詳細(xì),需要的朋友可以參考下2024-05-05