JavaWeb實現(xiàn)RSA+AES混合加密
一、前言
RSA與AES加密的詳細介紹這里就不寫了,網(wǎng)上很多博客,這里就只是簡單說明一下:
- AES:屬于對稱加密,通過一個公共的秘鑰,實現(xiàn)加密解密;
- RSA:非對稱加密,需要生成一個公鑰、一個私鑰,這兩個秘鑰使用時,一個用來加密時,那么就需要另一個秘鑰進行解密,公鑰一般提供給客戶端。
二、整體構(gòu)思
RSA+AES的混合加密時,AES用于給傳輸?shù)臄?shù)據(jù)加密,然后通過RSA給AES的秘鑰加密,所以接收到數(shù)據(jù)后,就需要先解密得到AES的秘鑰,然后通過AES秘鑰再去解密得到數(shù)據(jù)。
下面簡單說下demo中加密解密的實現(xiàn)過程:
- 前后端各自生成自己的RSA公私密鑰(這就必須確保雙方的RSA算法要匹配,不然雙方就無法正常解密)
- 當訪問項目首頁時,前端生成RSA秘鑰,并存放在window對象中的localStorage
- 頁面發(fā)起請求獲取服務端的RSA公鑰,服務端收到請求后生成RSA公司秘鑰,并將秘鑰放入session,所以每次建立會話連接時都是不一樣的秘鑰,然后將公鑰返回給前端頁面
- 頁面接收到服務端的RSA公鑰后,存入window對象,然后用服務端RSA公鑰加密前端的RSA公鑰發(fā)送給服務端
- 服務端收到前端發(fā)過來的請求后,通過自己的私鑰解密數(shù)據(jù),從而得到前端的公鑰,并存入session。
這里面提到的存儲秘鑰的方式只是在demo中作為演示使用,可以采用更合理、更安全的方式是實現(xiàn)!
這樣,前后端都擁有的對方RSA的公鑰,后面在同一個會話中具體的請求數(shù)據(jù)時,每次各自都會生成新的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的整數(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);
//使用密鑰初始化,設置為解密模式
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 {
/**
* 返回服務端的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中獲取服務端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);
// 添加響應頭(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:封裝的函數(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>
四、測試
啟動項目,打開首頁:

服務端日志:

當交換完秘鑰后,進行登錄測試:

服務端就收時的日志輸出:

響應后頁面的輸出:


以上就是一個簡單的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-06
springboot集成redis并使用redis生成全局唯一索引ID
本文主要介紹了springboot集成redis并使用redis生成全局唯一索引ID,文中通過示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-03-03
Mybatis報錯日志BindingException的解決
本文主要介紹了Mybatis報錯日志BindingException的解決,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2023-07-07
MybatisPlus實現(xiàn)數(shù)據(jù)權(quán)限隔離的示例詳解
Mybatis Plus對Mybatis做了無侵入的增強,非常的好用,今天就給大家介紹它的其中一個實用功能:數(shù)據(jù)權(quán)限插件,感興趣的可以跟隨小編一起了解下2024-04-04
Java中從Integer到Date的轉(zhuǎn)換方法
這篇文章主要介紹了Java中integer怎么轉(zhuǎn)換date,在Java中,如果我們有一個Integer類型的數(shù)據(jù),想要將其轉(zhuǎn)換為Date類型,本文給大家介紹了實現(xiàn)方法,并通過代碼示例講解的非常詳細,需要的朋友可以參考下2024-05-05

