JAVA的MD5
MD5是一种摘要算法,他又称哈希算法,数字指纹
1. 摘要算法目的是计算任意长度数据的摘要
2. 而它的输出,也就是他的摘要,是一个固定的长度,相同的数据我们始终得到相同的输出
3. 而不同的输入数据我们会尽量得到不同的输出
摘要算法的目的就是为了验证原始数据是否被篡改
如果我们的输入是任意长度的数据,比如一个byte数组,我们输出是一个固定长度的byte数组,我们就可以
把它称之为摘要算法,例如我们对hello字符串做一个摘要,如果他读到的是0x5e9182这个数,我们对hello,java
这个字符串做一个摘要算法,那么他就会得到一个不同的摘要输出,我们对hello,bob做一个摘要输出,
他就会得到一个不同的摘要输出
Java的Object.hashCode方法就是一个摘要算法,他的输入是一个任意的数据,而他的输出是一个任意长度的数据
实际上它是一个int类型,4个长度的byte类型数组,相同的输入必须要得到相同的输出,这也是为什么我们复写
equals方法的时候,我们必须正确的复写hashCode方法
我们再来看一下什么是碰撞?
碰撞是指两个不同的输入得到相同的输出,例如我们对abc和xyz都做哈希算法,我们可能会得到相同的输出,
这个时候我们就说发生了碰撞,碰撞能不能避免呢,碰撞是不能避免的,这是因为输出的字节长度是固定的,
而输入的长度是不固定的,所以哈希算法就是把一个无限的输入集合,到一个有限的输出集合,16个0到16个1
一共只有65536个输出,我们把无限的输入到65536个输出,那么肯定会有不同的输出到相同的输出的情况,
也就是碰撞
我们再来看一下哈希算法的安全性:
1. 首先一个好的哈希算法碰撞率要低
2. 我们不能够猜测输出,我们来对比右边的两种哈希算法,如果java001的哈希算法是123456,而java002的哈希算法是123457
那么我们就可以推算出java003的哈希算法是123458,这样的哈希算法是不安全的哈希算法,一个安全的哈希算法对于任意的
会造成输出的完全不同,因此安全的哈希算法很难从结果找出思路,只能通过暴力穷举
我们再来看一下常见的摘要算法:
1.有MD5,SHA-1,SHA-256,RipeMD-160,他们的输出长度是128bits,160bits,256bits,160bits,对应到字节数,MD5是16个字节,
SHA-1是20个字节,SHA-256是32个字节,RipeMD-160也是20个字节
在JAVA中使用MD5非常的简单,首先我们导入java.security.MessageDigest这个类,然后我们通过MessageDigest的方法,
传入MD5获取MessageDigest这个类,然后我们反复调用update方法,最后我们用digest()方法,获得16个字节长度的byte数组,
这个byte数组就是我们输入数据的最后输出
输入的数据是可以分片输入的,我们可以一次性输入helloworld,也可以把helloworld两个单词拆开,分别做update,
得到的结果是一样的
MD5可以用来验证文件的完整性,我们在网上下载文件的时候,例如我们在MYSQL的网站下载MYSQL Community Server,
MYSQL的网站会给出一个MD5值,我们下载完文件以后,通过计算MD5,和网站给出的MD5对比,就可以知道在下载的过程中,
文件有没有被损坏
MD5还可以存储用户的口令,如果我们有一个数据表,存放了username和password,我们的系统不存储原始口令,
而是存储原始口令的MD5,这样我们就不会在数据库中以明文的方式存储口令,那我们如何判断用户的口令是否正确呢
系统计算用户原始口令的MD5,并且与数据库存储的MD5对比,如果相同,说明口令正确,如果不同就说明口令错误
用MD5注意要避免彩虹表攻击,什么是彩虹表呢,彩虹表是预先计算好的,常用口令和MD5对照表,如果用户使用了常用口令,
那么黑客可以通过彩虹表,进行MD5就可以查出用户的原始口令,例如Bob,Alice,tim,我们存储的是MD5,但是黑客通过
彩虹表可以轻松的反查MD5的原始口令,分别是hello123,12345678,passwd0rd这样简单的原始口令,所以要抵御彩虹表的
攻击,我们不能直接原始口令的MD5,而是要对这个口令随机增加一个随机数,这个随机数通常称为salt,经过salt和原始口令
一块计算的MD5,就很难通过MD5再反查出来了
package com.learn.securl;
import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
/**
* 我们先看一下如何使用MD5
* @author Leon.Sun
*
*/
public class MD5Demo {
public static byte[] toMD5(byte[] input) {
MessageDigest md = null;
try {
/**
* 传入字符串MD5
* 获得一个MessageDigest的实例
*/
md = MessageDigest.getInstance("MD5");
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
/**
* 紧接着传入输入的byte
*/
md.update(input);
/**
* 最后我们用digest方法返回MD5
*/
return md.digest();
}
/**
* 然后我们使用一个main方法来使用MD5
* @throws Exception
*/
public static void main(String[] args) throws Exception {
/**
* 这里我们定义了一个字符串s
*/
String s = "MD5摘要算法测试";
/**
* 通过getBytes把做一个字符串变成一个字节数组
* 注意MD5的输入是字节而不是字符串
* 所以我们把一个字符串变成一个字节数组
*/
byte[] r = toMD5(s.getBytes("UTF-8"));
/**
* 最后我们要打印一个byte数组
* 如果我们直接打印byte数组
* 得到的是一个byte数组的地址
* 我们可以通过String.format传一个百分号032x
* 然后把一个byte数组变成一个BigInteger,
* 就可以使用16进制的形式打印数组
* c038cfbd1a587fcf4cbfe7d5a9094dca
* 我们看到MD5最后的结果是这样16进制表示的字节数组
*
*/
System.out.println(String.format("%032x", new BigInteger(1,r)));
}
}
package com.learn.securl;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class MD5SaltDemo {
public static byte[] toMD5(byte[] input) {
MessageDigest md = null;
try {
md = MessageDigest.getInstance("MD5");
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
md.update(input);
return md.digest();
}
public static void main(String[] args) throws Exception {
/**
* 假设我们的输入是我们用户的password
*/
String passwd = "helloworld";
/**
* 同时我们为了增强我们MD5的安全性
* 我们引入了一个随机的Salt
*/
String salt = "Random salt";
/**
* 然后把这两个字符串拼在一块
* 得到byte数组
* 然后再计算MD5
* 57de63c427b44d289128c3a8a496fb75
* 我们可以看到通过引入一个随机数salt,
* 我们可以把一个不安全的口令,
* 变为相对安全的MD5
*
*/
byte[] r = MD5SaltDemo.toMD5((salt+passwd).getBytes("UTF-8"));
System.out.println(String.format("%032x", new BigInteger(1,r)));
}
}
最后我们总结一下:
1. MD5是一种常用的哈希算法,他的输出是固定长度的,128bits,也就是16个字节
2. MD5常用于验证数据的完整性
3. 当我们使用MD5存储口令的时候,要考虑到彩虹表的攻击