Java双重MD5加密实现安全登录
一:问题引入
今天看到一篇文章说使用MD5对密码进行加密存储也还不能做到很安全,网上有在线解密MD5的网站,我一搜,还真有。接下来我尝试对我存储在数据库中的密码进行解密操作:
可以看到成功将我的密码解密出来,这让我很吃惊,因为我们都知道MD5算法是不可逆的,因为它是其是一种散列函数,使用的是hash算法,在计算过程中原文的部分信息是丢失了的。那么为什么网站中可以将我的密码解密出来呢?
经过一番查找后发现,原来在线解密工具的解密原理很简单,其原理是收集用户常用的简单密码形成了一个密码字典,并将字典中的密码用MD5加密后存储起来,在所谓的“解密“的时候,就将真正用户密码加密都的密文与已存储的密码相比较,如该密文存在于字典当中,即可以“解密”。因此,简单的用MD5对用户密码加密还不安全,我们设置密码时候也通常都会有复杂度检测,比如密码必须带英文数字什么的,就是为了安全性考虑。
知道其解密原理之后,我们尝试一下复杂点的密码看看能不能“解密”出来,首先对密码“qweasd666”进行加密:
加密之后我们选取32位小写的密文进行解密操作:
可以看到解密失败,说明其原理就是我们上面所说的收集常用的密文进行一一对应。既然解密失败了,那么说明“qweasd666”这个密码是安全的,你们可以都设置这个密码(doge)。
言归正传,说到这个网站成功将我精心设置的密码给破解了,这让我很没有安全感,而且我感觉我很没有面子,我一定要将我的登录安全等级进行提升。
除了上面提到的解密操作之外,还有一个很大的问题就是在前端将数据传输过来时候http采用的是明文传输,如果传输数据包被截取,那么就算你后端的加密算法有多复杂,你的密码也会被别人知道。
二:解决方案
2.1:第一次加密
找到问题所在之后,我们就可以对症下药了,首先我觉得要解决的是http明文传输问题,因为这个风险最大,普通抓包就能抓取密码,这不是很恐怖的一件事吗。解决方案也很简单,既然明文传输不安全,那么我们加密后再进行传输不就行了吗?
我选用的方案仍然还是MD5加密,但是对密码加密前还要加入一个固定salt。salt?是加盐吗,其实差不多,更不如说是加点“佐料”。其基本想法是这样的:当用户首次提供密码时(通常是注册时),由程序往这个密码里撒一些“佐料”,为了减轻开发压力,这个佐料对于每一个用户都是相同的,然后再散列。这样就能防止传输过程中出现明文密码泄露。
2.2:第二次加密
注意上面我提到的是防止出现明文密码泄漏,但是这并不代表密文不会泄漏,假如黑客截取你通过http传输的经过加密后的密码,或者数据库发生泄漏导致加密后的密码被黑客盗取,黑客通过解析前端js文件获得前端固定盐值,那么黑客可能可以通过彩虹表进行反向查询得到原始密码。这时候就二次加密的重要性就出来了,二次加密实现原理为对前端传过来经过加密之后的密码再次和一个随机Salt值结合后进行加密(注意这次是随机的),盐值会在用户登陆的时候随机生成,并存在数据库中。
这时候可能你会和我刚开始一样也会有一个疑惑,那就是你把随机盐值保存到数据库中了,那么如果数据库泄漏那你的随机盐值还有什么作用呢?黑客拿到加密数据进行解密后去除盐值不是就能得到密码了吗?是的,黑客有可能通过这样的手段得到密码,但是前提是他能解密出来,要知道的是MD5是无法直接破解的,只能通过穷举法进行解密。就算黑客拿到了数据库中的加密密码,但是不知道后端的加密过程,他就无法进行解析,就算黑客同时知道加密过程,由于经过了二次加盐二次加密,这时候的密文是很难很难被解析出来的,随着加密数据的复杂度增加,破解成本是呈指数级增加的,在那么大的成本面前相信没有什么黑客愿意去尝试。当然,salt也不一定要加在最前面或最后面,也可以插在中间,也可以分开插入,也可以倒序,程序设计时可以灵活调整,都可以使破解的难度呈指数型增长。
2.3:具体实现
2.3.1:用户注册
- 前端对用户输入的密码进行md5加密(固定的salt)
- 将加密后的密码传递到后端
- 后端随机生成一个salt
- 使用生成salt对前端传过来的密码进行加密,然后将加密后密码和salt一起保存到db中
2.3.2:用户登录
- 前端对用户输入的密码进行md5加密(固定的salt)
- 将加密后的密码传递到后端
- 后端使用用户账号取出用户信息
- 后端对加密后的密码在进行md5加密(取出盐),然后与数据库中存储的密码进行对比
- 匹配则登录成功,否则登录失败
三:代码实现
3.1:第一次加密
3.1.1:前端
const params = { ...this.ruleForm, sex: this.ruleForm.sex === '女' ? '0' : '1', //设置密码加密(加上固定salt值) password: md5(this.ruleForm.password + this.salt) } addEmployee(params).then(res => { if (res.code === 1) { this.$message.success('员工添加成功!') if (!st) { this.goBack() } else { this.ruleForm = { username: '', 'name': '', 'phone': '', password: '', // 'rePassword': '',/ 'sex': '男', 'idNumber': '' } } } else { this.$message.error(res.msg || '操作失败') } }
3.1.2:后端
/** * 添加员工 */ @PostMapping public R<String> save(@RequestBody Employee employee){ //生成随机salt值 String salt = RandomStringUtils.randomAlphanumeric(5); //设置随机盐值 employee.setSalt(salt); //设置密码二次加密 employee.setPassword(DigestUtils.md5DigestAsHex((salt + employee.getPassword()).getBytes())); boolean save = employeeService.save(employee); if(save){ return R.success("添加成功!"); } return R.error("添加失败!"); }
3.2:第二次加密
3.2.1:前端
const params = { ...this.loginForm, //登录密码加上固定盐值后发送 password: md5(this.loginForm.password + this.salt), } let res = await loginApi(params) if (String(res.code) === '1') { localStorage.setItem('userInfo', JSON.stringify(res.data)) window.location.href = '/backend/index.html' } else { this.$message.error(res.msg) this.loading = false }
3.2.2:后端
/** * 员工登录 */ @PostMapping("/login") public R<Employee> login(HttpServletRequest request, @RequestBody Employee employee){ //1.获取传输过来的加密后的密码值 String encryPassword = employee.getPassword(); //2.从数据库中获取用户信息 LambdaQueryWrapper<Employee> lqw = new LambdaQueryWrapper<>(); lqw.eq(Employee::getUsername,employee.getUsername()); Employee emp = employeeService.getOne(lqw); //3.如果没有查询到则返回登陆失败查询结果 if(emp == null){ return R.error("没有查询到该用户信息!"); } //4.获取注册时保存的随机盐值 String salt = emp.getSalt(); //5.将页面提交的密码password进行md5二次加密 String password = DigestUtils.md5DigestAsHex((salt + encryPassword).getBytes()); //6.密码比对,如果不一致则返回登陆失败结果 if(!emp.getPassword().equals(password)){ return R.error("密码错误!"); } //7.查看员工状态是否可用 if(emp.getStatus() == 0){ return R.error("该员工已被禁用!"); } //8.登录成功,将员工id存入Session对象并返回登录成功结果 request.getSession().setAttribute("employee",emp.getId()); return R.success(emp); }
到此这篇关于Java双重MD5加密实现安全登录的文章就介绍到这了,更多相关Java双重MD5加密 内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
相关文章
Spring启动过程中实例化部分代码的分析之Bean的推断构造方法
这篇文章主要介绍了Spring启动过程中实例化部分代码的分析之Bean的推断构造方法,实例化这一步便是在doCreateBean方法的 instanceWrapper = createBeanInstance(beanName, mbd, args);这段代码中,本文通过实例代码给大家介绍的非常详细,需要的朋友参考下吧2022-09-09
最新评论