流程图中左边为现有的登录,存在明文传输密码,通过抓包形式很容易破解,右图为RSA加密登录避免了密码明文传输,抓包获取不到真实密码,而且加密私钥一般在生成后五秒自动失效(自定义时间大小),破解难度大。
密钥生成工具类如下
package com.gaojin.common.utils;
import org.apache.commons.codec.binary.Base64;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import javax.crypto.Cipher;
import java.nio.charset.StandardCharsets;
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.concurrent.TimeUnit;
/**
* @author zhouq
* @date 2021/11/26
*/
@Component
public class RSAEncrypt {
@Resource
private RedisTemplate<String, Object> redisTemplate;
// 自定义redis的key
private static final String PREFIX = "gaojin-business:wmsPublicKey";
/**
* 随机生成密钥对
*
* @throws NoSuchAlgorithmException 异常
*/
public String genKeyPair(String user) throws NoSuchAlgorithmException {
// KeyPairGenerator类用于生成公钥和私钥对,基于RSA算法生成对象
KeyPairGenerator keyPairGen = KeyPairGenerator.getInstance("RSA");
// 初始化密钥对生成器,密钥大小为96-1024位
keyPairGen.initialize(1024, new SecureRandom());
// 生成一个密钥对,保存在keyPair中
KeyPair keyPair = keyPairGen.generateKeyPair();
// 得到私钥
RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
// 得到公钥
RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
String publicKeyString = new String(Base64.encodeBase64(publicKey.getEncoded()));
// 得到私钥字符串
String privateKeyString = new String(Base64.encodeBase64((privateKey.getEncoded())));
redisTemplate.opsForValue().set(PREFIX + user, privateKeyString, 5, TimeUnit.SECONDS);
return publicKeyString;
}
/**
* RSA私钥解密
*
* @param str 加密字符串
* @param privateKey 私钥
* @return 铭文
* @throws Exception 解密过程中的异常信息
*/
public static String decrypt(String str, String privateKey) throws Exception {
//64位解码加密后的字符串
byte[] inputByte = Base64.decodeBase64(str.getBytes(StandardCharsets.UTF_8));
//base64编码的私钥
byte[] decoded = Base64.decodeBase64(privateKey);
RSAPrivateKey priKey = (RSAPrivateKey) KeyFactory.getInstance("RSA").generatePrivate(new PKCS8EncodedKeySpec(decoded));
//RSA解密
Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.DECRYPT_MODE, priKey);
return new String(cipher.doFinal(inputByte));
}
}
以下为登陆方法需要修改的地方
@PostMapping("/login")
public AjaxResult login(@RequestBody LoginBody loginBody) throws Exception {
AjaxResult ajax = AjaxResult.success();
// 获取redis中用户对应的私钥,对密码进行解密,
String privateKey = (String) redisTemplate.opsForValue().get(PREFIX + loginBody.getUsername());
// 对前端加密后的密码进行解密,获取到真实的密码
String password = RSAEncrypt.decrypt(loginBody.getPassword(), privateKey);
// 将真实的密码设置到登录参数中
loginBody.setPassword(password);
// 生成令牌
String token = loginService.login(loginBody.getUserName(), loginBody.getPassword(),
loginBody.getUuid());
ajax.put(Constants.TOKEN, token);
return ajax;
}
@GetMapping("/getPublicKey")
public AjaxResult getPublicKey(String user) throws NoSuchAlgorithmException {
return AjaxResult.success(rsaEncrypt.genKeyPair(user));
}
前端登录方法按照以下写法
encryptedData(data, query) {
var publicKey = query
// 新建JSEncrypt对象
const encryptor = new JSEncrypt()
// 设置公钥
encryptor.setPublicKey(publicKey)
// 加密数据
this.loginForm.password = encryptor.encrypt(data)
},
handleLogin() {
this.$refs.loginForm.validate(valid => {
if (valid) {
this.loading = true
if (this.loginForm.rememberMe) {
Cookies.set('username', this.loginForm.username, { expires: 30 })
Cookies.set('password', encrypt(this.loginForm.password), { expires: 30 })
// Cookies.set('rememberMe', this.loginForm.rememberMe, { expires: 30 })
} else {
Cookies.remove('username')
Cookies.remove('password')
// Cookies.remove('rememberMe')
}
getPublicKey({ user: this.loginForm.username }).then(response => {
console.log(response)
this.encryptedData(this.loginForm.password, response.msg)
this.$store.dispatch('Login', this.loginForm).then(() => {
this.$router.push({ path: this.redirect || '/' }).catch(() => {})
}).catch(() => {
this.loading = false
// this.getCode()
})
})
}
})
}