不多說廢話,直接上代碼,這個調通的整個過程還是花了半天時間的,參考了大佬們的博客,親測可用
參考資料:
https://blog.csdn.net/qq_39027229/article/details/85003390
https://blog.csdn.net/weixin_42127766/article/details/82802189
https://blog.csdn.net/qq_39420411/article/details/94056654
博客園、CSDN同步更新
博客園:https://www.cnblogs.com/tangliping/p/14766846.html
CSDN:https://blog.csdn.net/qq_37023928/article/details/116777630
通過RSA實現密碼加密傳輸,核心思路:
- 點擊登錄,先請求后端,生成一對公私鑰,將公鑰返回給前台
- 前台使用開源的jsencrypt.js對密碼進行加密,加密后傳輸到后台
- 后台對加密的密碼進行解密
一、項目依賴
使用的是thymeleaf模板進行整合的。
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.8.RELEASE</version>
<relativePath/>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!--StringUtils等工具類-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<!--fastjson-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.76</version>
</dependency>
<!--thymeleaf啟動器-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.4.5</version>
</plugin>
</plugins>
</build>
二、前端代碼
獲取開源的js文件:https://github.com/travist/jsencrypt
js文件在上面開源項目的bin目錄下。文件名稱為:jsencrypt.js
https://github.com/travist/jsencrypt/tree/master/bin
獲取到開源文件后,我們把它放在:static/js/
templates/login.html文件:
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<script src="http://code.jquery.com/jquery-1.8.3.min.js"></script>
<script src="js/jsencrypt.js"></script>
<body>
<h1 th:text="${msg}">RSA測試</h1>
<form>
用戶賬號:
<input type="text" name="username" id="username">
<br>
用戶密碼:
<input type="text" name="password" id="password">
<br>
<input type="button" th:onclick="login()" th:value="提交">
</form>
</body>
<script>
function login() {
var username = $('#username').val();
var password = $('#password').val();
var encrypt = new JSEncrypt();
$.ajax({
type: "get", //提交方式
url: "/getPublicKey",//訪問路徑
contentType: 'application/json;charset=utf-8',//返回json結果
success: function (data) {
console.log(data)
encrypt.setPublicKey(data)
var encryptPwd = encrypt.encrypt(password)
console.log("encryptPwd:"+encryptPwd)
$.ajax({
type: "post", //提交方式
url: "/loginRequest",//訪問路徑
contentType: 'application/json;charset=utf-8',//返回json結果
data: JSON.stringify({"username":username,"password":encryptPwd}),
success: function (data) {
console.log(data)
}
});
}
});
}
</script>
</html>
三、RSA工具類
/**
* RSA工具類
* 參考:https://blog.csdn.net/qq_39027229/article/details/85003390
* https://blog.csdn.net/weixin_42127766/article/details/82802189
* https://blog.csdn.net/qq_39420411/article/details/94056654
* 備注,解密前台公鑰加密的數據,請調用decryptWithPrivate方法。
* 每次重啟之后,都會生成一個一對新的公私鑰
*/
public class RSAUtil {
//秘鑰大小
private static final int KEY_SIZE = 1024;
//后續放到常量類中去
public static final String PRIVATE_KEY = "privateKey";
public static final String PUBLIC_KEY = "publicKey";
private static KeyPair keyPair;
private static Map<String,String> rsaMap;
//生成RSA,並存放
static {
try {
Provider provider =new org.bouncycastle.jce.provider.BouncyCastleProvider();
Security.addProvider(provider);
SecureRandom random = new SecureRandom();
KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA", provider);
generator.initialize(KEY_SIZE,random);
keyPair = generator.generateKeyPair();
//將公鑰和私鑰存放,登錄時會不斷請求獲取公鑰,我們可以將其放到緩存中,而不放入數據庫了
//我在想,這個是不是有必要存放到Redis,在分布式場景中?
//貌似有些必要,萬一獲取到的pubkey是server1中的,拿着server1的pubkey去server2去解密?
storeRSA();
} catch(NoSuchAlgorithmException e) {
e.printStackTrace();
}
}
/**
* 將RSA存入緩存
*/
private static void storeRSA() {
rsaMap = new HashMap<>();
PublicKey publicKey = keyPair.getPublic();
String publicKeyStr = new String(Base64.encodeBase64(publicKey.getEncoded()));
rsaMap.put(PRIVATE_KEY, publicKeyStr);
PrivateKey privateKey = keyPair.getPrivate();
String privateKeyStr = new String(Base64.encodeBase64(privateKey.getEncoded()));
rsaMap.put(PUBLIC_KEY, privateKeyStr);
}
/**
* 私鑰解密(解密前台公鑰加密的密文)
*
* @param encryptText 公鑰加密的數據
* @return 私鑰解密出來的數據
* @throws Exception e
*/
public static String decryptWithPrivate(String encryptText) throws Exception {
if(StringUtils.isBlank(encryptText)){
return null;
}
byte[] en_byte = Base64.decodeBase64(encryptText.getBytes());
//byte[] en_byte = Hex.decode(encryptText);
Provider provider = new org.bouncycastle.jce.provider.BouncyCastleProvider();
Security.addProvider(provider);
//之前我只寫了RSA,出現了亂碼+明文 參考:https://blog.csdn.net/qq_39420411/article/details/94056654
Cipher ci = Cipher.getInstance("RSA/ECB/PKCS1Padding", provider);
PrivateKey privateKey = keyPair.getPrivate();
ci.init(Cipher.DECRYPT_MODE, privateKey);
byte[] res = ci.doFinal(en_byte);
return new String(res);
}
/**
* java端 使用公鑰加密(此方法暫時用不到)
* @param plaintext 明文內容
* @return byte[]
* @throws UnsupportedEncodingException e
*/
public static byte[] encrypt(String plaintext) throws UnsupportedEncodingException {
String encode = URLEncoder.encode(plaintext, "utf-8");
RSAPublicKey rsaPublicKey = (RSAPublicKey)keyPair.getPublic();
//獲取公鑰指數
BigInteger e = rsaPublicKey.getPublicExponent();
//獲取公鑰系數
BigInteger n = rsaPublicKey.getModulus();
//獲取明文字節數組
BigInteger m = new BigInteger(encode.getBytes());
//進行明文加密
BigInteger res = m.modPow(e, n);
return res.toByteArray();
}
/**
* java端 使用私鑰解密(此方法暫時用不到)
* @param cipherText 加密后的字節數組
* @return 解密后的數據
* @throws UnsupportedEncodingException e
*/
public static String decrypt(byte[] cipherText) throws UnsupportedEncodingException {
RSAPrivateKey prk = (RSAPrivateKey) keyPair.getPrivate();
// 獲取私鑰參數-指數/系數
BigInteger d = prk.getPrivateExponent();
BigInteger n = prk.getModulus();
// 讀取密文
BigInteger c = new BigInteger(cipherText);
// 進行解密
BigInteger m = c.modPow(d, n);
// 解密結果-字節數組
byte[] mt = m.toByteArray();
//轉成String,此時是亂碼
String en = new String(mt);
//再進行編碼,最后返回解密后得到的明文
return URLDecoder.decode(en, "UTF-8");
}
/**
* 獲取公鑰
* @return 公鑰
*/
public static String getPublicKey(){
return rsaMap.get(PUBLIC_KEY);
}
/**
* 獲取私鑰
* @return 私鑰
*/
public static String getPrivateKey(){
return rsaMap.get(PRIVATE_KEY);
}
public static void main(String[] args) throws UnsupportedEncodingException {
System.out.println(RSAUtil.getPrivateKey());
System.out.println(RSAUtil.getPublicKey());
byte[] usernames = RSAUtil.encrypt("username66");
System.out.println(RSAUtil.decrypt(usernames));
}
}
四、控制層
@RestController
public class RSAController {
@RequestMapping("/getPublicKey")
public String getPublicKey(){
return RSAUtil.getPublicKey();
}
}
@Controller
public class LoginController {
@RequestMapping("/login")
public String login(Model model){
model.addAttribute("msg", "RSA前端加密,后端解密測試");
return "login";
}
@RequestMapping(value = "/loginRequest",method= RequestMethod.POST)
@ResponseBody
public String loginRequest(@RequestBody JSONObject json){
String username = json.getString("username");
String password = json.getString("password");
System.out.println(username);
System.out.println(password);
String res = null;
try {
res = RSAUtil.decryptWithPrivate(password);
//這里就是解密后的密碼了
System.out.println(res);
} catch (Exception e) {
e.printStackTrace();
}
return res;
}
}
五、測試
好了,上述結果就是解密成功的結果了。