一:MD5简介
Message Digest Algorithm MD5(中文名为消息摘要算法第五版)为计算机安全领域广泛使用的一种散列函数,用以提供消息的完整性保护, 用于确保信息传输完整一致。
把一个任意长度的字节串变换成一定长的十六进制数字串,注意是十六进制。是计算机广泛使用的杂凑算法之一(又译摘要算法、哈希算法), 注意MD5是属于哈希算法不属于加密算法。
MD5算法具有以下特点:
1、压缩性:任意长度的数据,算出的MD5值长度都是固定的。MD5加密后的值是128bit的,按4位二进制组合成一个十六进制,所以最后出来的十六进制字符串是32个,比如e10adc3949ba59abbe56e057f20f883e。
2、容易计算:从原数据计算出MD5值很容易。
3、抗修改性:对原数据进行任何改动,哪怕只修改1个字节,所得到的MD5值都有很大区别。
4、强抗碰撞:已知原数据和其MD5值,想找到一个具有相同MD5值的数据(即伪造数据)是非常困难的。注意是非常困难并不是不可能,事实上已经有很多这种情况了。
二:MD5应用
(1)一致性验证:
MD5的典型应用是对一段信息(Message)产生信息摘要(Message-Digest),以防止被篡改。比如,在Unix下有很多软件在下载的时候都有一个文件名相同,文件扩展名为.md5的文件,在这个文件中通常只有一行文本,大致结构如:MD5 (tanajiya.tar.gz) = 38b8c2c1093dd0fec383a9d9ac940515这就是tanajiya.tar.gz文件的数字签名。MD5将整个文件当作一个大文本信息,通过其不可逆的字符串变换算法,产生了这个唯一的MD5信息摘要。
为了让读者朋友对MD5的应用有个直观的认识,笔者以一个比方和一个实例来简要描述一下其工作过程:
大家都知道,地球上任何人都有自己独一无二的指纹,这常常成为司法机关鉴别罪犯身份最值得信赖的方法;与之类似,MD5就可以为任何文件(不管其大小、格式、数量)产生一个同样独一无二的“数字指纹”,如果任何人对文件做了任何改动,其MD5值也就是对应的“数字指纹”都会发生变化。
我们常常在某些软件下载站点的某软件信息中看到其MD5值,它的作用就在于我们可以在下载该软件后,对下载回来的文件用专门的软件(如Windows MD5 Check等)做一次MD5校验,以确保我们获得的文件与该站点提供的文件为同一文件。
具体来说文件的MD5值就像是这个文件的“数字指纹”。每个文件的MD5值是不同的,如果任何人对文件做了任何改动,其MD5值也就是对应的“数字指纹”就会发生变化。比如下载服务器针对一个文件预先提供一个MD5值,用户下载完该文件后,用我这个算法重新计算下载文件的MD5值,通过比较这两个值是否相同,就能判断下载的文件是否出错,或者说下载的文件是否被篡改了。
利用MD5算法来进行文件校验的方案被大量应用到软件下载站、论坛数据库、系统文件安全等方面。
(2) 数字签名
MD5的典型应用是对一段Message(字节串)产生fingerprint(指纹),以防止被“篡改”。举个例子,你将一段话写在一个叫 readme.txt文件中,并对这个readme.txt产生一个MD5的值并记录在案,然后你可以传播这个文件给别人,别人如果修改了文件中的任何内容,你对这个文件重新计算MD5时就会发现(两个MD5值不相同)。如果再有一个第三方的认证机构,用MD5还可以防止文件作者的“抵赖”,这就是所谓的数字签名应用。
(3) 安全访问认证
MD5还广泛用于操作系统的登陆认证上,如Unix、各类BSD系统登录密码、数字签名等诸多方面。如在Unix系统中用户的密码是以MD5(或其它类似的算法)经Hash运算后存储在文件系统中。当用户登录的时候,系统把用户输入的密码进行MD5 Hash运算,然后再去和保存在文件系统中的MD5值进行比较,进而确定输入的密码是否正确。通过这样的步骤,系统在并不知道用户密码的明码的情况下就可以确定用户登录系统的合法性。这可以避免用户的密码被具有系统管理员权限的用户知道。MD5将任意长度的“字节串”映射为一个128bit的大整数,并且是通过该128bit反推原始字符串是困难的,换句话说就是,即使你看到源程序和算法描述,也无法将一个MD5的值变换回原始的字符串,从数学原理上说,是因为原始的字符串有无穷多个,这有点象不存在反函数的数学函数。所以,要遇到了md5密码的问题,比较好的办法是:你可以用这个系统中的md5()函数重新设一个密码,如admin,把生成的一串密码的Hash值覆盖原来的Hash值就行了。
正是因为这个原因,现在被黑客使用最多的一种破译密码的方法就是一种被称为”跑字典”的方法。有两种方法得到字典,一种是日常搜集的用做密码的字符串表,另一种是用排列组合方法生成的,先用MD5程序计算出这些字典项的MD5值,然后再用目标的MD5值在这个字典中检索。我们假设密码的最大长度为8位字节(8 Bytes),同时密码只能是字母和数字,共26+26+10=62个字节,排列组合出的字典的项数则是P(62,1)+P(62,2)….+P(62,8),那也已经是一个很天文的数字了,存储这个字典就需要TB级的磁盘阵列,而且这种方法还有一个前提,就是能获得目标账户的密码MD5值的情况下才可以。这种加密技术被广泛的应用于Unix系统中,这也是为什么Unix系统比一般操作系统更为坚固一个重要原因。
三:工具类
public class MD5Util {
private static final String hexDigits[] = { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9",
"a", "b", "c", "d", "e", "f" };
private static String byteArrayToHexString(byte b[]) {
StringBuffer resultSb = new StringBuffer();
for (int i = 0; i < b.length; i++) {
resultSb.append(byteToHexString(b[i]));
}
return resultSb.toString();
}
private static String byteToHexString(byte b) {
int n = b;
if (n < 0) {
n += 256;
}
int d1 = n / 16;
int d2 = n % 16;
return hexDigits[d1] + hexDigits[d2];
}
public static String encode(String origin) {
return encode(origin, "UTF-8");
}
public static String encode(String origin, String charsetname) {
String resultString = null;
try {
resultString = new String(origin);
MessageDigest md = MessageDigest.getInstance("MD5");
if (charsetname == null || "".equals(charsetname)) {
resultString = byteArrayToHexString(md.digest(resultString.getBytes()));
} else {
resultString = byteArrayToHexString(md.digest(resultString.getBytes(charsetname)));
}
} catch (Exception e) {
e.printStackTrace();
}
return resultString;
}
public static void main(String[] args) {
System.out.println(MD5Util.encode("123456"));
}
}
四:缺点
同一个MD5值现在比较容易找到多个原始值,如下有几个链接,多张图片同时计算出相同的md5值,因此使用MD5值作来验证文件一致性已经不安全了。Linux发行版,比如openSUSE、Fedora,其ISO文件的CHECKSUM目前是带签名SHA-256, 如果文件的数字签名,其摘要算法为MD5,应保持警惕.
- 两张图片,相同MD5(需要翻墙,要使用原图)
https://crypto.stackexchange.com/questions/1434/are-there-two-known-strings-which-have-the-same-md5-hash-value - 三张图片,相同MD5(需要翻墙,要使用原图)
- 两个相同MD5的可执行文件 http://natmchugh.blogspot.com/2015/05/how-to-make-two-binaries-with-same-md5.html
- 两个相同MD5的PHP文件 http://natmchugh.blogspot.com/2014/10/how-i-made-two-php-files-with-same-md5.html
对于安全访问认证,有些密码经常使用MD5(password + salt)作为密码,理论上这种方式已经不安全了,很多公司已经不再采用这种方式了。
MD5的优点是快,对于来源可靠的有限环境,MD5依然是可用的。对于封闭的内部环境,加盐Hash依然是可用的,但这种“加强”是脆弱的。
在公开的环境下,比如网盘、内容分发,MD5已经变得非常危险。至于SHA-1,目前还没有已知的、公开的碰撞案例,但在理论上已存在风险。
以Wikipedia的举例,几种Hash方法的参考性能为:
- MD5:335MiB/s
- SHA-1:192MiB/s
- SHA-256:139MiB/s
- SHA-512:154MiB/s
SHA256Util
SHA256和MD5都属于散列算法,SHA256的长度为64个字符,MD5为32个字符长度
public class SHA256Util {
/**
* 利用Apache的工具类实现SHA-256加密
* @param str 加密后的报文
* @return
*/
public static String getSHA256Str(String str){
MessageDigest messageDigest;
String encdeStr = "";
try {
messageDigest = MessageDigest.getInstance("SHA-256");
byte[] hash = messageDigest.digest(str.getBytes("UTF-8"));
encdeStr = Hex.encodeHexString(hash);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return encdeStr;
}
/**
* 利用java原生的摘要实现SHA256加密
* @param str 加密后的报文
* @return
*/
public static String sha256(String str){
MessageDigest messageDigest;
String encodeStr = "";
try {
messageDigest = MessageDigest.getInstance("SHA-256");
messageDigest.update(str.getBytes("UTF-8"));
encodeStr = byte2Hex(messageDigest.digest());
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return encodeStr;
}
/**
* 将byte转为16进制
* @param bytes
* @return
*/
private static String byte2Hex(byte[] bytes){
StringBuffer stringBuffer = new StringBuffer();
String temp = null;
for (int i=0;i<bytes.length;i++){
temp = Integer.toHexString(bytes[i] & 0xFF);
if (temp.length()==1){
// 1得到一位的进行补0操作
stringBuffer.append("0");
}
stringBuffer.append(temp);
}
return stringBuffer.toString();
}
public static void main(String[] args) {
System.out.println(SHA256Util.sha256("123456"));
System.out.println(SHA256Util.getSHA256Str("123456"));
}
}