Spring Boot使用RSA模拟简单的登录加密功能
什么是RSA?
RSA是一种公钥密码算法,它的名字由三位开发者,即Ron Rivest、Adi Shamir和Leonard Adleman的姓氏的首字母组成的。
RSA被用于公钥密码和数字签名。
RSA被认为是非常安全的,不过计算速度要比DES慢很多。同DES一样,其安全性也从未被证明过,但想攻破RSA算法涉及的大数(至少200位的大数)的因子分解是一个极其困难的问题。所以,由于缺乏解决大数的因子分解的有效方法,因此,可以推测出目前没有有效的办法可以破解RSA。
RSA加密和解密的过程
通过一对密钥(公钥和私钥)实现的加密和解密,通过公钥对明文进行加密,使用时再使用私钥进行解密即可。
RSA加密和解密的实现
编写一个工具类,实现方法:创建公钥和私钥、通过公钥加密、通过私钥解密。
package cn.qhj.backtoweb.util;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.tomcat.util.codec.binary.Base64;
import javax.crypto.Cipher;
import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
/**
* @author QHJ
* @date 2021/11/30 14:21
* @description: RSA加密
*/
public class RSAUtils {
protected static final Log log = LogFactory.getLog(RSAUtils.class);
// 加密的方式
private static String KEY_RSA_TYPE = "RSA";
// 转换 算法/反馈模式/填充方案 RSA/ECB/PKCS1Padding (1024, 2048)
// 加密算法有:AES,DES,DESede(DES3)和RSA 四种
// 模式有CBC(有向量模式)和ECB(无向量模式),向量模式可以简单理解为偏移量,使用CBC模式需要定义一个IvParameterSpec对象
private static String KEY_RSA_TYPE_ALL = "RSA/ECB/PKCS1Padding";
// JDK方式 RSA加密最大只有 1024位
private static int KEY_SIZE = 1024;
// 模长
private static int ENCODE_PART_SIZE = KEY_SIZE / 8;
// 公钥
public static final String PUBLIC_KEY_NAME = "public";
// 私钥
public static final String PRIVATE_KEY_NAME = "private";
/**
* 创建公钥和私钥
* @return
*/
public static Map<String, String> createRSAKeys(){
// 用来存放公钥和私钥的 Base64位加密
Map<String, String> keyPairMap = new HashMap<String, String>();
try {
// 生成公钥和私钥对——给予 RSA算法生成对象
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(KEY_RSA_TYPE);
// 使用给定的随机源(以及默认参数集)初始化特定密钥大小的密钥对生成器
keyPairGenerator.initialize(KEY_SIZE, new SecureRandom());
// 生成密钥对
KeyPair keyPair = keyPairGenerator.generateKeyPair();
// 返回对此密钥对的公钥组件的引用
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
// 返回对此密钥对的私钥组件的引用
RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
// 得到公钥字符串和秘钥字符串
String publicKeyValue = Base64.encodeBase64String(keyPair.getPublic().getEncoded());
String privateKeyValue = Base64.encodeBase64String(keyPair.getPrivate().getEncoded());
// 存入公钥和私钥,以便以后获取
keyPairMap.put(PUBLIC_KEY_NAME, publicKeyValue);
keyPairMap.put(PRIVATE_KEY_NAME, privateKeyValue);
} catch (NoSuchAlgorithmException e) {
log.error("当前JDK版本没有找到RSA加密算法!");
e.printStackTrace();
}
return keyPairMap;
}
/**
* 公钥加密
* 描述:1字节 = 8 位,最大加密长度为 128 - 11 = 117 字节,不管多长数据,加密出来都是128字节的长度
* @param str 加密字符串
* @param publicKey 公钥
* @return 密文
* @throws Exception 加密过程中的异常信息
*/
public static String encryptByPublicKey(String str, String publicKey){
// base64 编码的公钥
byte[] publicBytes = Base64.decodeBase64(publicKey);
// 公钥加密——按照X509标准对其进行编码的密钥 复制数组的内容,以防随后进行修改。
X509EncodedKeySpec x509EncodedKeySpec = new X509EncodedKeySpec(publicBytes);
// 已经加密过的数据
List<byte[]> encodedDataList = new LinkedList<byte[]>();
// 最大加密长度 1024/8/8 - 11
int maxEncodeSize = ENCODE_PART_SIZE - 11;
// 所有加密的数据
String encodeBase64Result = null;
try {
// 密钥工厂——用于将密钥(Key类型的不透明加密密钥)转换成密钥规范(底层密钥材料的透明表示)
KeyFactory keyFactory = KeyFactory.getInstance(KEY_RSA_TYPE);
// 根据提供的密钥规范(密钥材料)生成公钥对象
PublicKey publicKeys = keyFactory.generatePublic(x509EncodedKeySpec);
// 为加密和解密提供密码功能(所传递的参数:转换)
Cipher cipher = Cipher.getInstance(KEY_RSA_TYPE_ALL);
// 用密钥初始化此Cipher ENCRYPT_MODE:用于将 Cipher初始化为解密模式的常量
cipher.init(Cipher.ENCRYPT_MODE, publicKeys);
byte[] strBytes = str.getBytes("utf-8");
// 获取所有被加密数据长度
int strLen = strBytes.length;
// 如果明文长度大于 模长-11 则要分组加密
for (int i = 0;i < strLen;i += maxEncodeSize){
int curPosition = strLen - i;
int tempLen = curPosition;
if (curPosition > maxEncodeSize){
tempLen = maxEncodeSize;
}
// 待加密分段数据
byte[] tempBytes = new byte[tempLen];
System.arraycopy(strBytes, i, tempBytes, 0, tempLen);
byte[] tempEncodedData = cipher.doFinal(tempBytes);
encodedDataList.add(tempEncodedData);
}
// 加密次数
int partLen = encodedDataList.size();
// 所有加密的长度
int allEncodedLen = partLen * ENCODE_PART_SIZE;
// 存放所有 RSA分段加密数据
byte[] encodeData = new byte[allEncodedLen];
for (int i = 0;i < partLen;i++){
byte[] tempByteList = encodedDataList.get(i);
System.arraycopy(tempByteList, 0, encodeData, i * ENCODE_PART_SIZE, ENCODE_PART_SIZE);
}
encodeBase64Result = Base64.encodeBase64String(encodeData);
} catch (Exception e) {
e.printStackTrace();
}
return encodeBase64Result;
}
/**
* 私钥解密
* @param str 解密字符串
* @param privateKey 私钥
* @return 明文
* @throws Exception 解密过程中的异常信息
*/
public static String decryptByPrivateKey(String str, String privateKey){
byte[] privateBytes = Base64.decodeBase64(privateKey);
byte[] encodeStr= Base64.decodeBase64(str);
// 要解密的数据长度
int encodePartLen = encodeStr.length / ENCODE_PART_SIZE;
List<byte[]> decodeListData = new LinkedList<byte[]>();
// 所有解密的数据
String decodeStrResult = null;
// 私钥解密
PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(privateBytes);
try {
// 密钥工厂——用于将密钥(Key类型的不透明加密密钥)转换成密钥规范(底层密钥材料的透明表示)
KeyFactory keyFactory = KeyFactory.getInstance(KEY_RSA_TYPE);
// 根据提供的密钥规范(密钥材料)生成私钥对象
PrivateKey privateKeys = keyFactory.generatePrivate(pkcs8EncodedKeySpec);
// 为加密和解密提供密码功能(所传递的参数:转换)
Cipher cipher = Cipher.getInstance(KEY_RSA_TYPE_ALL);
// 用密钥初始化此Cipher ENCRYPT_MODE:用于将 Cipher初始化为解密模式的常量
cipher.init(Cipher.DECRYPT_MODE, privateKeys);
// 初始化所有被解密数据长度
int allDecodeByteLen = 0;
for (int i = 0;i < encodePartLen;i++){
// 待解密数据分段
byte[] tempEncodedData = new byte[ENCODE_PART_SIZE];
System.arraycopy(encodeStr, i * ENCODE_PART_SIZE, tempEncodedData, 0, ENCODE_PART_SIZE);
byte[] decodePartData = cipher.doFinal(tempEncodedData);
decodeListData.add(decodePartData);
allDecodeByteLen += decodePartData.length;
}
byte[] decodeResultBytes = new byte[allDecodeByteLen];
for (int i = 0, curPosition = 0;i < encodePartLen;i++){
byte[] tempStrBytes = decodeListData.get(i);
int tempStrBytesLen = tempStrBytes.length;
System.arraycopy(tempStrBytes, 0, decodeResultBytes, curPosition, tempStrBytesLen);
curPosition += tempStrBytesLen;
}
decodeStrResult = new String(decodeResultBytes, "UTF-8");
} catch (Exception e) {
e.printStackTrace();
}
return decodeStrResult;
}
}
模拟简单登录加密功能
-
编写控制层
/** * @author QHJ * @date 2021/11/30 18:19 * @description: 模拟登录(密码加密解密) */ @Controller public class LoginController { /** * 登录之前生成秘钥 * @param request * @return */ @RequestMapping(value = "/login") public String login(HttpServletRequest request, HttpServletResponse response) throws IOException { Map<String, String> rsaKeys = RSAUtils.createRSAKeys(); String publicKey = rsaKeys.get(RSAUtils.PUBLIC_KEY_NAME); System.out.println("publicKey:" + publicKey); String privateKey = rsaKeys.get(RSAUtils.PRIVATE_KEY_NAME); System.out.println("privateKey:" + privateKey); // 存入session HttpSession session = request.getSession(); session.setAttribute("publicKey", publicKey); session.setAttribute("privateKey", privateKey); return "redirect:/toIndex"; } /** * 跳转到登录页面 */ @RequestMapping(value = "/toIndex") public String toIndex(){ return "index"; } /** * 执行登陆操作 * @param username 用户名 * @param password 加密后的密码 * @param request * @return */ @ResponseBody @RequestMapping(value = "/doLogin", method = RequestMethod.POST) public String doLogin(String username, String password, HttpServletRequest request) { // 获取 session中的 privateKey String privateKey = (String) request.getSession().getAttribute("privateKey"); String decryptPwd = RSAUtils.decryptByPrivateKey(password, privateKey); if ("zhangsan123".equals(decryptPwd)){ return "登录成功!"; } return "登录失败!"; } }
-
编写简单页面
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>模拟登录</title> </head> <body> <form action="/doLogin" method="post" name="user" onsubmit="fun()"> 用户名:<input name="username" type="text"><br> 密码:<input name="password" type="password" id="pwd"><br> <button type="submit">登录</button> </form> <script type="text/javascript" src="js/jsencrypt.min.js"></script> <script> function fun(){ // 获取 name为 user的 form元素下面的 name为 password的元素的值 // 获取 form 表单中的密码 var pwd = user.password.value; // 创建 JSEncrypt 对象 var encrypt = new JSEncrypt(); // 获取 session中的 publicKey encrypt.setPublicKey('[[${session.publicKey}]]'); // 对密码加密,并且放入 input的value值中 document.getElementsByName("password")[0].value = encrypt.encrypt(pwd); return true; } </script> </body> </html>
-
测试登录