常见哈希算法总结实现:

        1.什么是哈希算法:

        哈希算法又称摘要算法,它的作用是:对任意一组输入数据进行计算,得到一个固定长度的输出摘要。哈希算法的目的:为了验证原始数据是否被篡改。

        哈希算法最重要的特点就是:

        ● 相同的输入一定得到相同的输出;

        ● 不同的输入大概率得到不同的输出(基本可忽略)

        ● 单向性:给定一个输入数,容易计算出它的哈希值,但是已知一个哈希值根据同样的算法不能得到原输入数

        例:Java字符串的hashCode()就是一个哈希算法,它的输入是任意字符串,输出是固定的4字节int整数:

System.out.println("Hello,see you tomorrow".hashCode()); //972450381
System.out.println("Hello,see you tomorrow".hashCode()); //972450381
System.out.println("hello,see you tomorrow".hashCode()); //179158573

我们可以看到   ① 只要输入信息一致,结果肯定是完全一样

                       ② 如果输入有哪怕一点点小小的不一样,结果会完全不同,并且完全无法预测。

                       ③ 只是把Hello的第一个字母改成小写的h,两者的结果完全看不出联系。

        2.哈希碰撞:

        哈希碰撞是指,两个不同的输入得到了相同的输出:

"AaAaAa".hashCode(); // 0x7460e8c0
"BBAaBB".hashCode(); // 0x7460e8c0

"通话".hashCode(); // 0x11ff03
"重地".hashCode(); // 0x11ff03

哈希算法还可能出现冲突吗?答案是:是的

        比如说:Java中String的hashCode()输出是4字节整数,最多只有4294967296种输出,但输入的数据长度是不固定的,有无数种输入。所以,哈希算法是把一个无限的输入集合映射到一个有限的输出集合,存在产生碰撞的可能。

        碰撞不可怕,我们担心的不是碰撞,而是碰撞的概率,因为碰撞概率的高低关系到哈希算法的安全性。一个安全的哈希算法必须满足: 碰撞概率低、不能猜测输出。不能猜测输出是指:输入的任意一个字符的变化会造成输出完全不同,这样就很难从输出反推输入(而且是在知道算法如何加密的前提下)

        3.目前常用的哈希加密算法

      算法         输出长度(位)           输出长度(字节)
      MD5         128 bits                 16 bytes
    SHA-1         160 bits                 20 bytes
 RipeMD-160         160 bits                 20 bytes
   SHA-256         256 bits                 32 bytes
   SHA-512         512 bits                 64 bytes

        根据碰撞概率,哈希算法的输出长度越长,就越难产生碰撞,也就越安全,但同时可能要承担带来的时间复杂度变大的影响。Java标准库提供了常用的哈希算法,并且有一套统一的接口。这里我们以MD5算法为例,看看如何对输入计算哈希:

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
public class MD5Test {
	public static void main(String[] args) throws NoSuchAlgorithmException {
		//获取基于MD5加密算法的工具对象
		MessageDigest md5 = MessageDigest.getInstance("MD5");
		//更新原始数据
		md5.update("hello".getBytes());
		md5.update("world".getBytes());
		//获取加密后的结果
		byte[] resultArray = md5.digest();
		System.out.println(Arrays.toString(resultArray));
		//输出:-4, 94, 3, -115, 56, -91, 112, 50, 8, 84, 65, -25, -2, 112, 16, -80
		
		//只要内容向同,加密结果就相同
		MessageDigest tempMd5 = MessageDigest.getInstance("MD5");
		tempMd5.update("helloworld".getBytes());
		byte[] tempResultArray = tempMd5.digest();
		System.out.println(Arrays.toString(tempResultArray));
		//同样输出:-4, 94, 3, -115, 56, -91, 112, 50, 8, 84, 65, -25, -2, 112, 16, -80		
	}
}
将结果字节数组中的内容转化为16进制字符串结果为:fc5e038d38a57032085441e7fe7010b0

经过MD5加密算法处理,输入HelloWorld得到的MD5是:fc5e038d38a57032085441e7fe7010b0

        4.哈希算法的用途:

        一、校验本地文件:因为相同的输入永远会得到相同的输出,因此,如果输入被修改了,得到的输出就会不同。我们在网站上下载软件的时候,经常看到下载页显示的MD5哈希值:

常见哈希算法总结实现:_第1张图片

         如何判断下载到本地的软件是原始的、未经篡改的文件?我们只需要自己计算一下本地文件的哈希值,再与官网公开的哈希值对比,如果相同,说明文件下载正确,否则,说明文件已被篡改。

        二、哈希算法的另一个重要用途是存储用户口令。如果直接将用户的原始口令存放到数据库中,会产生极大的安全风险: 数据库管理员能够看到用户明文口令、数据库数据一旦泄漏,黑客即可获取用户明文口令。

        那么不存储用户的原始口令,那么如何对用户进行认证?方法是存储用户口令的哈希,例如,MD5。在用户输入原始口令后,系统计算用户输入的原始口令的MD5并与数据库存储的MD5对比,如果一致,说明口令正确,否则,口令错误。

        当然使用哈希口令时,还要注意防止彩虹表攻击。什么是彩虹表呢?如果只拿到MD5,从MD5反推明文口令,只能使用暴力穷举的方法。然而黑客并不笨,暴力穷举会消耗大量的算力和时间。但是,如果有一个预先计算好的常用口令和它们的MD5的对照表,这个表就是彩虹表。如果用户使用了常用口令,黑客从MD5一下就能反查到原始口令:

常用口令 MD5
hello123 f30aa7a662c728b7407c54ae6bfd27d1
12345678 25d55ad283aa400af464c76d713c07ad
passw0rd bed128365216c019988915ed3add75fb
19700101 570da6d5277a646f6552b8832012f5dc
...... ......

        这也正是为什么app在我们初次注册密码时如果密码存在一些"规律",系统就会提醒我们当前密码强度太弱的原因,当然,我们也可以采取特殊措施来抵御彩虹表攻击:对每个口令额外添加随机数,这个方法称之为加盐(salt): digest = md5(salt + inputPassword),加盐后的内容基本上可以达到无规律了,加强了密码的安全性。

        最后再说另外一个也比较常见的hash算法 SHA-1

        SHA-1也是一种哈希算法,它的输出是160 bits,即20字节。SHA-1是由美国国家安全局开发的,SHA算法实际上是一个系列,包括SHA-0(已废弃)、SHA-1、SHA-256、SHA-512等。 在Java中使用SHA-1,和MD5完全一样,只需要把算法名称改为"SHA-1":

//SHA-1算法加密一张图片
public class MD5Test2 {
	public static void main(String[] args) throws NoSuchAlgorithmException, IOException {
		MessageDigest md5 = MessageDigest.getInstance("SHA-1");
		byte[] b = Files.readAllBytes(Paths.get("C:\\Users\\肖\\Pictures\\Saved Pictures\\22535.jpg"));		
		md5.update(b);
		//加密结果为一个字节数组
		byte[] resultArr = md5.digest();
		System.out.println(Arrays.toString(resultArr));
		StringBuilder sb = new StringBuilder();
		//将字节数组中的内容转化为16进制的字符串
		for(byte bi:resultArr) {
			sb.append(String.format("%02x", bi));
		}		
		System.out.println(sb);	
	}
}

//输出结果
/*	 [-87, -26, -54, -109, 122, 35, 27, 119, 3, 65, 58, 50, 59, 60, 120, 31, 32, 3, 12, 96]
	 a9e6ca937a231b7703413a323b3c781f20030c60
 */

你可能感兴趣的:(java,算法,哈希算法,eclipse)