关于RSA加密,虽然密码学的课还没有教到RSA加密,但是网上这么多资料还是很方便能学习一下RSA的原理的。
AI给我的代码是2048bit的长度的密钥,太安全了哥们,量子计算机来了都破解不了这一块。
| 算法描述 | 变量表示 |
|---|---|
| 1.选择两个大质数p,q | p,q |
| 2.计算n=p*q | n |
| 3.欧拉公式φ(n)=(p-1)(q-1) | φ(n) |
| 4.选择一个整数e,使得1<e<φ(n),且e和φ(n)互质 | e |
| 5.计算e关于φ(n)的模逆元d,即e*d≡1(mod φ(n)) | d |
pk=(e, n),私钥sk=(d, n)给定明文M,加密过程如下:
- 计算C≡Me (mod n),得到的C就是密文。
给定密文C,解密过程如下:
- 计算M≡Cd (mod n),得到的M就是解密后的明文。
来证明一下这个算法:
C≡Me (mod n)≡(Cd (mod n))e(mod n)≡Cde(mod n)
而由于e*d≡1(mod φ(n)) 带入我们们可以得到C1+kφ(n)(mod n)
所以我们要证明C≡C1+kφ(n)(mod n)
1.假设C与n互质
原式=C1+kφ(n)(mod n)=C*Ckφ(n)(mod n)
欧拉定理指出,如果 C 和 n 互素,则 C 的 ϕ(n) 次幂模 n 等于 1。
所以我们得到Cϕ(n)≡1(mod n) 所以(Cϕ(n))k(mod n)≡1 (mod n)
得证C*Ckφ(n)(mod n)=C*1 (mod n)=C
2.假设C与n不互质
设 C 是 p 的倍数,则 C = xp,其中 x 为正整数, 那么 gcd(m, q) = 1,由欧拉定理得:
Cφ(q) ≡ 1(mod q)
那么
Cφ(q) ≡ 1(mod q)
(Cφ(q))φ(p) ≡ 1φ(p) (mod q)
(Cφ(q))φ(p) ≡ 1 (mod q)
Ckφ(n) ≡ 1(mod q)
所以,会存在一个整数 r,使得 Ckφ(n) ≡ 1 + rq ,两边同时乘以 C = xp 得:
Ckφ(n)+1 = C + rxpq = C + rxn
即 Ckφ(n)+1 ≡C (mod n) 得证
搞定了证明,我们就可以把他搬进我们的代码里了。
/RsaUtil.java
package com.example.newtest.util;
import javax.crypto.Cipher;
import java.security.*;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
public class RsaUtil {
// 生成RSA密钥对
public static KeyPair generateKeyPair() throws Exception {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
keyPairGenerator.initialize(2048);
return keyPairGenerator.generateKeyPair();
}
// 获取公钥字符串
public static String getPublicKeyString(PublicKey publicKey) {
return Base64.getEncoder().encodeToString(publicKey.getEncoded());
}
// 从字符串加载公钥
public static PublicKey loadPublicKey(String publicKeyStr) throws Exception {
byte[] keyBytes = Base64.getDecoder().decode(publicKeyStr);
X509EncodedKeySpec spec = new X509EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
return keyFactory.generatePublic(spec);
}
// 从字符串加载私钥
public static PrivateKey loadPrivateKey(String privateKeyStr) throws Exception {
byte[] keyBytes = Base64.getDecoder().decode(privateKeyStr);
PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
return keyFactory.generatePrivate(spec);
}
// RSA加密
public static String encrypt(String data, PublicKey publicKey) throws Exception {
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] encryptedBytes = cipher.doFinal(data.getBytes("UTF-8"));
return Base64.getEncoder().encodeToString(encryptedBytes);
}
// RSA解密
public static String decrypt(String encryptedData, PrivateKey privateKey) throws Exception {
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.DECRYPT_MODE, privateKey);
byte[] dataBytes = Base64.getDecoder().decode(encryptedData);
byte[] decryptedBytes = cipher.doFinal(dataBytes);
return new String(decryptedBytes, "UTF-8");
}
}
this is RSA密码生成器,这里封装了6个函数,都是用于RSA加密解密功能的实现。
@RestController
public class LoginController {
@Autowired
private LoginService loginService;
@Autowired
private HttpServletRequest request;
// 移除静态密钥对
// 获取RSA公钥接口
@GetMapping("/public-key")
public Map<String, String> getPublicKey(HttpSession session) throws Exception {
// 为每个session生成独立的密钥对
KeyPair keyPair = RsaUtil.generateKeyPair();
session.setAttribute("rsaKeyPair", keyPair);
Map<String, String> result = new HashMap<>();
result.put("publicKey", RsaUtil.getPublicKeyString(keyPair.getPublic()));
return result;
}
@PostMapping("/login")
public result login(@RequestBody user user, HttpSession session) {
String username = user.getUsername();
String password = user.getPassword();
String captcha = user.getCaptcha();
// 验证验证码
String sessionCaptcha = (String) session.getAttribute("captcha");
if (sessionCaptcha == null || !sessionCaptcha.equalsIgnoreCase(captcha)) {
return result.captchaError();
}
// 验证成功后清除session中的验证码
session.removeAttribute("captcha");
try {
// 从session获取对应的密钥对
KeyPair keyPair = (KeyPair) session.getAttribute("rsaKeyPair");
if (keyPair == null) {
return result.error("密钥已过期,请刷新页面重试");
}
// 使用私钥解密密码
String decryptedPassword = RsaUtil.decrypt(password, keyPair.getPrivate());
// 解密后立即清除密钥对
session.removeAttribute("rsaKeyPair");
return loginService.login(username, decryptedPassword);
} catch (Exception e) {
e.printStackTrace();
return result.error("密码解密失败,请重试");
}
}
}
LoginController这边代码很清楚调用函数解密验证,但是做到这里我感觉到全部把密钥对放入session中是不安全的,所以考虑使用静态的密钥对,让私钥不离开服务器会更加安全。AI给出的方案是使用密钥池,但是我感觉严重拖累性能了,需要尝试所有的私钥解密,这是人类能想出来的方法吗?好吧AI确实不是人类,改用单密钥定期更换会好一点。
做到这里其实也是可以的,但是我在实装之后发现:哇!怎么卡成这个样子了,整个登录会变慢了特别多,RSA虽然是非常安全,但是运算量太大会导致严重影响的服务器性能。所以要使用AES加密数据,然后将密钥经过RSA公钥加密后传输到服务器,这样既保证了安全,也减少了对性能的影响。

Comments NOTHING