RSA 是什麼
RSA 是一種非對稱加密算法,簡單概括,就是加密和解密時使用不同的密鑰進行。
問題
一般在進行WEB開發過程中,少不了用戶登錄功能的開發,用戶在登錄時需要輸入用戶名(或郵箱等其它唯一標識信息)和密碼進行系統登錄。如果密碼通過明文的方式進行http傳輸並登錄時,密碼將能被人查看到。此時多數人會想到MD5編碼,通過將密碼進行MD5編碼成無法解密的密文,則他人就算獲取到該密文,也無法解密獲取其原始密碼。但後臺需要獲取該原始密碼進行校驗登錄的操作,那麼MD5編碼不可取。搜索相關如何保證賬號密碼安全的文章,瞭解到RSA這種非對稱加密算法。
方案確定
網上的許多Jsencrypt庫進行前端加密,java後臺解密的代碼,但只適用於公鑰和私鑰確定的情況。本文爲提高安全性,目標是後臺動態生成公鑰私鑰對。
整體方案:後臺動態生成RSA的密鑰,將公鑰發送給前端,並將私鑰通過session保存在服務器,讓前端進行加密操作後纔將密文傳給後端,後端使用對應私鑰進行解密操作。
前端:TypeScript
後端:Java
前端(JavaScript/TypeScript)加密實踐
前端進行RSA加密的第三方庫採用 node-forge 庫,其他如 Jsencrypt 庫等暫時沒有調試成功。
前端代碼:
import * as forge from 'node-forge';
// publicKey需要先通過http從後臺獲取,後臺可以寫一些geKey接口供前端調用
const pki = forge.pki;
// 規定格式:publicKey之前需要加'-----BEGIN PUBLIC KEY-----\n',之後需要加'\n-----END PUBLIC KEY-----'
const publicK = pki.publicKeyFromPem('-----BEGIN PUBLIC KEY-----\n' + publicKey + '\n-----END PUBLIC KEY-----');
// forge通過公鑰加密後一般會是亂碼格式,可進行base64編碼操作再進行傳輸,相應的,後臺獲取到密文的密碼後需要先進行base64解碼操作再進行解密
const passwordCrypto = forge.util.encode64(publicK.encrypt(password));
// ... 後面就是進行常規的發送登錄請求,不同的是,也需要將publicKey作爲一個參數傳輸到後臺,後臺需要以此找到對應的私鑰
後端(Java)解密實踐
網上後端解密的代碼很多,但質量無法確保,正好我使用的 Hutool 這個Java的工具庫,其中包含了非對稱加密的工具,可以直接使用。
Java代碼:
/**
* 獲取祕鑰-RSA
* @return
*/
@RequestMapping(value = "/getKey",method = RequestMethod.POST)
@ResponseBody
public Result<String> getKeyOfRSA() {
Result<String> result = new Result<>();
RSA rsa = new RSA();
String privateKeyBase64 = rsa.getPrivateKeyBase64();
String publicKeyBase64 = rsa.getPublicKeyBase64();
// 使用當前用戶的session進行保存公鑰私鑰對
Subject currentUser = SecurityUtils.getSubject();
Session session = currentUser.getSession();
session.setAttribute(publicKeyBase64, privateKeyBase64);
return result.successWithData(publicKeyBase64);
}
/**
* 解密password
* @param password
* @param publicKey
* @return
* @throws Exception
*/
private String checkPassword(String password, String publicKey, String algorithm) throws Exception {
Subject currentUser = SecurityUtils.getSubject();
Session session = currentUser.getSession();
// publicKey需要前端返回
String privateKey = (String) session.getAttribute(publicKey);
if(privateKey ==null){
log.error("session中私鑰失效.publickey={},password={}",publicKey,password);
return null;
}
// 獲取到私鑰之後,需要將session中的該公鑰私鑰對信息移除
session.removeAttribute(publicKey);
// 構建,當只用私鑰進行構造對象時,只允許使用該私鑰進行加密和解密操作,本文只需要進行私鑰解密,故只使用私鑰構造對象
RSA rsa = new RSA(privateKey, null);
// 密碼的密文先進行base64解碼,之後再進行解密
byte[] decrypt = rsa.decrypt(Base64.decode(password), KeyType.PrivateKey);
String decryptStr = StrUtil.str(decrypt, CharsetUtil.CHARSET_UTF_8);
return decryptStr;
}