单向加密

MD5

  • Message Digest algorithm 5,信息摘要算法也称为哈希算法、散列算法,可以将任意长度的数据转换成一个定长的、不可逆的数字
    • 一般用于确保信息的传输完整一致性,校验传输的数据是否被修改,一旦原始信息被修改,生成的 MD5 值将会变得很不同
    • 算法能将任意大小、格式的文字或文件进行加密从而产生 128 bit(16 字节)的散列值。如同人的指纹,不同文本的 MD5 值是不同的
    • 极端情况:就是不同的字符串的 MD5 值一样,这叫哈希碰撞。2009 年中科院就已经实现了相应的碰撞算法,不过 MD5 应用仍然很广泛
  • 但是只要明文相同,那么生成的MD5码就相同,于是攻击者就可以通过撞库的方式来破解出明文。加盐就是向明文中加入随机数,然后再生成MD5,这样一来即使明文相同,但由于随机数是不同(极少相同),所以每次生成的MD5码也不同,如此一来就大大增加了暴力破解的难度,使其几乎不可能破解
  • 前端使用JavaScript生成MD5
1
<script src="https://cdn.bootcdn.net/ajax/libs/blueimp-md5/2.18.0/js/md5.min.js"></script>
  • 加盐后生成的序列是无法通过解密工具解密出来的,可以测试一下https://www.cmd5.com/
1
2
3
4
5
6
7
// MD5加密
var v1= md5("admin");
console.log(v1) // 21232f297a57a5a743894a0e4a801fc3

// 加盐,拼接随机的字符串,生成的字符串通过解密工具是无法解密出的
var v2= md5("admin"+"0x3d1eaaa");
console.log(v2) //58d49490d8c6614eaa3ca046300a6be9

加密应用

MD5应用

  • 登录注册使用MD5进行密码的加密和解密应用

    • 如果不使用加密,用户以明文的方式在客户端提交,通过网关到达服务器。用户数据走的是HTTP协议,数据未加密!黑客就可以在网关通过嗅探的形式进行抓包获取用户数据
    • 前端方案:在用户提交数据时对数据进行MD5加密,但有可能被破解,所以需要进行加盐处理
    • 后端方案:后端服务器收到用户数据后,如果未加密,黑客直接入侵服务器获取到webshell然后拿到数据库的权限,黑客第一件事就是拖库、扒数据(数据是核心)。如果服务器数据不加密,直接获取内容可能就是明文数据,造成密码泄露!
  • 首先数据库准备,密码要设置唯一并且可变,比如varchar(50),长度要大一点

  • 前端要在数据提交前进行MD5加盐操作,提交给服务器的就是一串类似于以下字符串

    • f43bbfbdf3a2aa4afc82ec1d552367f4
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
<h1>登录</h1>
<span id="msg"></span>
<form id="loginForm">
用户名:<input type="text" id="name" name="name">
密码:<input type="password" id="password" name="password">
<button>登录</button>
</form>
<script src="https://cdnjs.cloudflare.com/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.min.js"></script>
<script>
$('#loginForm').submit(function () {
var name = $('#name').val()
var password = $('#password').val()
if(name.length>1&&password.length>1){
// 发送请求对密码进行md5+salt
var salt = "0xbklj"
var hexPass = md5(password+salt)
console.log(hexPass)
// 发送Ajax
$.ajax({
type:'post',
url:'/login',
data:{
"name":name,
"password":hexPass
},
success:function (res) {
if(res.code== 100){
$('#msg').html("<font color='green'>登录成功</font>")
}else{
$('#msg').html("<font color='red'>登录失败</font>")
}
},
error:function () {

}
})
}else{
$('#msg').html("<font color='red'>信息有误!</font>")
}
return false
})
</script>
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
<html>
<html>
<meta charset="utf-8">
<title>注册</title>
<link href="/xxx" rel="stylesheet"/>
</head>
<body>
<h1>注册</h1>
<span id="msg"></span>
<form id="registerForm">
用户名:<input type="text" id="name" name="name">
密码:<input type="password" id="password" name="password">
<button>注册</button>
</form>
<script src="https://cdnjs.cloudflare.com/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.min.js"></script>
<script>
$('#registerForm').submit(function () {
var name = $('#name').val()
var password = $('#password').val()
if(name.length>1&&password.length>1){
// 发送请求对密码进行md5+salt
var salt = "0xbklj"
var hexPass = md5(password+salt)
console.log(hexPass)
// 发送Ajax
$.ajax({
type:'post',
url:'/register',
data:{
"name":name,
"password":hexPass
},
success:function (res) {
if(res.code== 100){
$('#msg').html("<font color='green'>注册成功</font>")
}else{
$('#msg').html("<font color='red'>注册失败</font>")
}
},
error:function () {

}
})
}else{
$('#msg').html("<font color='red'>信息有误!</font>")
}
return false
})
</script>
</body>
</html>
  • 用户注册成功后,MD5加密后的密码生成的字符串保存在数据库中,那么防止黑客入侵数据库获取密码进行破解,所以需要对前端传来的数据进行二次MD5加密,在数据库中存入二次加密得到的字符串
  • 编写工具类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
package com.lzy.springbootdemo.utils;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

public class MD5Utils {
// 盐值
public static final String SALT = "xiaoliblog";
// 16进制字符
public static String[] chars = {"0","1","2","3","4","5","6","7","8","9","a","b","c","d","e","f"};

// MD5加密
public static String StringInMd5(String str,String salt){
// 消息签名(摘要)
MessageDigest md5 = null;
// 对字符串进行加盐拼接
String hexStr = str+salt;
try{
// 参数代表算法1名称
md5 = MessageDigest.getInstance("md5");
byte[] result = md5.digest(hexStr.getBytes());
StringBuilder sbr = new StringBuilder(32);
// 将结果转为16进制字符 0~9 A~F
for (int i = 0; i < result.length; i++) {
// 一个字节对应两个字符
byte x = result[i];
// 取得高位
int h = 0x0f & (x >>> 4);
// 取得低位
int l = 0x0f & x;
sbr.append(chars[h]).append(chars[l]);
}
return sbr.toString();
}catch (NoSuchAlgorithmException e){
throw new RuntimeException(e);
}
}
}
  • 自定义返回信息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
public class Msg {
//状态码 100-成功 200-失败
private int code;
//提示信息
private String msg;

//用户要返回给浏览器的数据
private Map<String, Object> extend = new HashMap<String, Object>();

public static Msg success(){
Msg result = new Msg();
result.setCode(100);
result.setMsg("处理成功!");
return result;
}

public static Msg fail(){
Msg result = new Msg();
result.setCode(200);
result.setMsg("处理失败!");
return result;
}
//实现链式调用
public Msg add(String key,Object value){
this.getExtend().put(key, value);
return this;
}

public int getCode() {
return code;
}

public void setCode(int code) {
this.code = code;
}

public String getMsg() {
return msg;
}

public void setMsg(String msg) {
this.msg = msg;
}

public Map<String, Object> getExtend() {
return extend;
}

public void setExtend(Map<String, Object> extend) {
this.extend = extend;
}
}
  • 用户控制器编写,在修改和查询数据时调用工具类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
package com.lzy.springbootdemo.controller;

@Controller
public class UserController {
@Autowired
private UserMapper userMapper;

// 视图跳转
@RequestMapping("/tologin")
public String toLogin(){
return "login";
}
@RequestMapping("/toregister")
public String toRegister(){
return "register";
}

// 处理注册
@RequestMapping("/register")
@ResponseBody
public Msg Register(HttpServletRequest request){
String name = request.getParameter("name");
String password = request.getParameter("password");
System.out.println("服务器未加密的密码:"+password);

// 前端传来的一次加密的密码进行MD5二次加密
String hexPass = MD5Utils.StringInMd5(password,MD5Utils.SALT);
System.out.println("服务器加密后的密码:"+hexPass);

User user = new User();
user.setName(name);
user.setPassword(hexPass);
int result = userMapper.insert(user);
if(result > 0 ){
return Msg.success().add("msg","注册成功");
}
return Msg.fail().add("msg","注册失败");
}

// 处理登录
@RequestMapping("/login")
@ResponseBody
public Msg Login(HttpServletRequest request){
String name = request.getParameter("name");
String password = request.getParameter("password");
System.out.println("服务器未加密的密码:"+password);
// 因为用户注册后服务器进行了二次加密,数据库保存的是二次加密的数据
// 所以查询用户,也要根据二次加密后的密码查询
String hexPass = MD5Utils.StringInMd5(password,MD5Utils.SALT);
System.out.println("服务器加密后的密码:"+hexPass);

// MyBatis-plus 条件查询,根据用户名和密码查询
HashMap<String,Object> map = new HashMap();
map.put("name",name);
map.put("password",hexPass);
List<User> userList = userMapper.selectByMap(map);
if(userList.size() == 0 ){
return Msg.fail().add("msg","此用户未注册");
}
return Msg.success().add("msg","登录成功");
}
}
  • 服务器未二次加密时
    • 如果用户提交的加密字符序列是9899ed5f15a24475d634182c46ff5c1c,那么数据库中保存的也是此序列,很有可能被黑客入侵数据并破解
    • 前端
      • md5+salt值:0xbkl ==>> 9899ed5f15a24475d634182c46ff5c1c
  • 服务器二次加密后
    • 对用户一次加密的字符序列,进行二次加盐加密生成另一个字符序列存入数据库中,黑客得到的是二次加密的字符序列,因为第一次在前端通过脚本加密很可能被破解
    • 后端
      • 得到前端的序列:9899ed5f15a24475d634182c46ff5c1c
      • 再次md5+slat值:xiaoliblog ==>> a8320b26554deecc3bc1b5e6af29138b(数据库保存的结果)
  • 可以看到前端和后端都有盐值,用户注册后并不需要知道盐值是多少,因为在业务逻辑中自动把用户密码和盐值拼接了,这也是MD5实现验证的精髓!!!!