Hutool配合jsencrypt进行RSA加解密前言前

这是我参与11月更文挑战的第5天,活动详情查看:2021最后一次更文挑战

前言

本节介绍RuoYi-Vueruoyi-admin模块中的个人信息模块SysProfileController 部分的代码,
今天我们讲解一下个人信息模块这里有意思的地方。我们来讨论一下重置密码这里,前台密码应该如何向后台传递。

/**
 * 重置密码
 */
@PutMapping("/updatePwd")
public AjaxResult updatePwd(String oldPassword, String newPassword) {
    LoginUser loginUser = getLoginUser();
    String userName = loginUser.getUsername();
    String password = loginUser.getPassword();
    if (!SecurityUtils.matchesPassword(oldPassword, password)) {
        return AjaxResult.error("修改密码失败,旧密码错误");
    }
    if (SecurityUtils.matchesPassword(newPassword, password)) {
        return AjaxResult.error("新密码不能与旧密码相同");
    }
    if (userService.resetUserPwd(userName, SecurityUtils.encryptPassword(newPassword)) > 0) {
        // 更新缓存用户密码
        loginUser.getUser().setPassword(SecurityUtils.encryptPassword(newPassword));
        tokenService.setLoginUser(loginUser);
        return AjaxResult.success();
    }
    return AjaxResult.error("修改密码异常,请联系管理员");
}
复制代码

在这里我们可以看到传入的是用户输入的密码,前端并没有任何操作,没有加密或散列,而是在后台才用SecurityUtils.encryptPassword对用户的密码进行的加密。

image.png
那么就会有一个很有趣的问题,前台密码应该如何向后台传递?

前台密码如何向后台传递

前台密码如何向后台传递是一个很有意思的问题,方法很多,有直接向后端传递的,有通过md5加密之后向后台传递的,也有通过RSA加密之后再后端解密的

直接传递

直接传递明文密码是最简单的方法,但是同时也会带来一定的风险,如果你不是用的https,那么对于路径上的所有人来说你的密码都是透明的,即使这个网站可能并没有什么重要作用,恶意者也可以用这个账号密码去其他网站进行撞库操作。或者打印日志时也是有可能能够将你的密码打印出来的,毕竟像ruoyi中直接将密码放入了url中,如果Nginx里面记录了访问日志,那么你的密码就处于泄露的边缘了。

MD5摘要算法

我们使用Hutool来模拟一下后端数据库已经存在的被MD5的密码,

<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.7.15</version>
</dependency>
复制代码

前端要用的MD5js我们用blueimp-md5

https://www.bootcdn.cn/blueimp-md5/
<script src="https://cdn.bootcdn.net/ajax/libs/blueimp-md5/2.18.0/js/md5.js"></script>
复制代码

我们先建立一个前端的html,里面对我们要传递后台的密码进行一次MD5,当然也可以为了安全进行两次MD5。

image.png

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">   
    <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
    <script src="https://cdn.bootcdn.net/ajax/libs/blueimp-md5/2.18.0/js/md5.js"></script>
</head>
<body>
<button>md5发送请求</button>
<script>
    $("button").click(function () {
        var password = md5("123456");
        $.ajax({
            type: "POST",
            url: "/api/testMd5",
            data: {password: password}
        }).done(function (msg) {
            alert("Data Saved: " + msg);
        });
    });
</script>

</body>
</html>
复制代码

后端使用接口进行接收并和后端md5之后的数据进行比较,我们这里使用的是HutoolDigestUtil.md5Hex方法。

@PostMapping("/testMd5")
public boolean testMd5(@RequestParam("password") String password) {
    String backPassword = DigestUtil.md5Hex("123456");
    if (backPassword.equals(password)) {
        return true;
    } else {
        return false;
    }
}
复制代码

使用MD5的好处就是简单方便,但是坏处是我们在后端无法知晓用户使用的密码是否符合我们的要求,只能在前端验证一番,因为MD5之后我们无法知晓用户输入的究竟是什么。而且md5带来的问题还有如果用户使用的是常见的密码的话,有可能被碰撞出来,对于用户的其他网站的安全性带来了隐患。

RSA加密解密

前端我们加密的RSAjs使用jsencrypt

https://www.bootcdn.cn/jsencrypt/
<script src="https://cdn.bootcdn.net/ajax/libs/jsencrypt/3.2.1/jsencrypt.js"></script>
复制代码

官网是travistidwell.com/jsencrypt/
我们首先需要生成RSA的公钥和密钥
可以使用www.metools.info/code/c80.ht… 来进行生成

image.png
我选择的长度是1024,格式是PKCS#8,它增加了验证数据段,保证密钥正确性。

前端公钥加密

进行RSA加密,我们要在前端暴露一个公钥出去,我们这里为了简单,直接使用一个固定的公钥。

image.png
jsencrypt加密之后生成的是一个base64的字符串,可以通过代码中解密部分的参数注释发现,解密的时候是将这个base64转成了一个hex,

image.png

var encrypt = new JSEncrypt();
debugger
encrypt.setPublicKey($("#publicKey").html());
var encrypted = encrypt.encrypt("123456");
复制代码

通过上面的代码,我们就将密码进行了公钥加密,现在就是传到后台进行解密了。

后台私钥解密

后台解密我们同样使用Hutool进行
本来我以为会很简单,因为前端按照公钥加密已经完成,Hutool也给了私钥解密的例子,抄一下就行了呗。

image.png
但是坏事就坏在HexUtil.decodeHex这里了,这里的String a实际上是16进制字符串,我这边传入的是base64字符串,一开始我用

byte[] aByte = HexUtil.decodeHex(Base64.decodeStr(password));
复制代码

报错了,原因很简单Base64.decodeStr给的并不是16进制字符串,用HexUtil.decodeHex肯定就报错了,变不成字节数组,但是没有注释的情况下我还以为不兼容,反复试了很多种,直到我跟踪前端js代码解密部分

image.png
把这个ret里面的值作为HexUtil.decodeHex的参数,这里成功的解密,我才察觉出这个问题,一搜base64tohex,吼吼,第一个就是我想要的,直接用原生的就可以拿到byte数组了,根本没必要再转一个hex

byte[] decoded = Base64.getDecoder().decode(password);
复制代码

这样,我们的RSA加解密就完成了!后端可以知道用户输入的密码是否符合我们要求的密码格式和强度,同时在传递过程中也不会泄露。