Java哈希算法

哈希算法

哈希算法

1.概述

哈希算法(Hash)又称摘要算法(Digest),它的作用是:对任意一组输入数据进行计算,得到一个固定长度的输出摘要。

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

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

● 不同的输入大概率得到不同的输出。

所以,哈希算法的目的:为了验证原始数据是否被篡改。

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

java 复制代码
"hello".hashCode(); // 0x5e918d2
"hello, java".hashCode(); // 0x7a9d88e8
"hello, bob".hashCode(); // 0xa0dbae2f

两个相同的字符串永远会计算出相同的hashCode,否则基于hashCode定位的HashMap就无法正常工作。这也是为什么当我们自定义一个class时,覆写equals()方法时我们必须正确覆写hashCode()方法。

2.哈希碰撞

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

例如:

java 复制代码
"AaAaAa".hashCode(); // 0x7460e8c0
"BBAaBB".hashCode(); // 0x7460e8c0

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

碰撞能不能避免?答案是不能。碰撞是一定会出现的,因为输出的字节长度是固定的,String的hashCode()输出是4字节整数,最多只有4294967296种输出,但输入的数据长度是不固定的,有无数种输入。所以,哈希算法是把一个无限的输入集合映射到一个有限的输出集合,必然会产生碰撞。

碰撞不可怕,我们担心的不是碰撞,而是碰撞的概率,因为碰撞概率的高低关系到哈希算法的安全性。一个安全的哈希算法必须满足:

● 碰撞概率低;

● 不能猜测输出。

不能猜测输出是指:输入的任意一个bit的变化会造成输出完全不同,这样就很难从输出反推输入(只能依靠暴力穷举)。

假设一种哈希算法有如下规律:

java 复制代码
hashA("java001") = "123456"
hashA("java002") = "123457"
hashA("java003") = "123458"

那么很容易从输出123459反推输入,这种哈希算法就不安全。安全的哈希算法从输出是看不出任何规律的:

java 复制代码
hashB("java001") = "123456"
hashB("java002") = "580271"
hashB("java003") = ???

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算法为例,看看如何对输入内容计算哈希:

java 复制代码
import java.security.MessageDigest;

public class main {
	public static void main(String[] args)  {
		// 创建一个MessageDigest实例:
        MessageDigest md = MessageDigest.getInstance("MD5");
       
        // 反复调用update输入数据:
        md.update("Hello".getBytes("UTF-8"));
        md.update("World".getBytes("UTF-8"));
        
        // 16 bytes: 68e109f0f40ca72a15e05cc22786f8e6
        byte[] results = md.digest(); 

        StringBuilder sb = new StringBuilder();
        for(byte bite : results) {
        	sb.append(String.format("%02x", bite));
        }
        
        System.out.println(sb.toString());
	}
}

使用MessageDigest时,我们首先根据哈希算法获取一个MessageDigest实例,然后,反复调用update(byte[])输入数据。当输入结束后,调用digest()方法获得byte[]数组表示的摘要,最后,把它转换为十六进制的字符串。

运行上述代码,可以得到输入HelloWorld的MD5是68e109f0f40ca72a15e05cc22786f8e6。

4.哈希算法的用途

4.1校验下载文件

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

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

4.2存储用户密码

哈希算法的另一个重要用途是存储用户口令。如果直接将用户的原始口令存放到数据库中,会产生极大的安全风险:

● 数据库管理员能够看到用户明文口令;

● 数据库数据一旦泄漏,黑客即可获取用户明文口令。

username password
admin admin
user user
tim tim

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

因此,数据库存储用户名和口令的表内容应该像下面这样:

username password
admin 25f9e794323b453885f5181f1b624d0b
user 73a90acaae2b1ccc0e969709665bc62f
tim 19f9f30bd097d4c066d758fb01b75032

这样一来,数据库管理员看不到用户的原始口令。即使数据库泄漏,黑客也无法拿到用户的原始口令。想要拿到用户的原始口令,必须用暴力穷举的方法,一个口令一个口令地试,直到某个口令计算的MD5恰好等于指定值。

使用哈希口令时,还要注意防止彩虹表攻击。

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

常用口令 MD5
hello123 f30aa7a662c728b7407c54ae6bfd27d1
12345678 25d55ad283aa400af464c76d713c07ad
... ...

这就是为什么不要使用常用密码,以及不要使用生日作为密码的原因。

当然,我们也可以采取特殊措施来抵御彩虹表攻击:对每个口令额外添加随机数,这个方法称之为加盐(salt):

digest = md5(salt + inputPassword)

经过加盐处理的数据库表,内容如下:

username salt password
bob H1r0a a5022319ff4c56955e22a74abcc2c210
alice 7$p2w e5de688c99e961ed6e560b972dab8b6a
tim z5Sk9 1eee304b92dc0d105904e7ab58fd2f64
复制代码
public class Demo05 {
	public static void main(String[] args) throws NoSuchAlgorithmException, IOException {
		byte[] password = "liyupiicu@com".getBytes();
		MessageDigest md5 = MessageDigest.getInstance("MD5");
		md5.update(password);

		// 产生随机盐值
		String uuid = UUID.randomUUID().toString().substring(16);
		md5.update(uuid.getBytes());
		// 获取消息摘要对象进行加密
		byte[] digetByte = md5.digest();
		System.out.println(Arrays.toString(digetByte));
		System.out.println(Demo03.byteToHex(digetByte));
		System.out.println(digetByte.length);
	}
}

MD5加密

Demo01

java 复制代码
public class Demo01 {
	public static void main(String[] args) throws NoSuchAlgorithmException {
		// 创建一个MessageDigest实例:
		MessageDigest md = MessageDigest.getInstance("MD5");

		byte[] bytes = "测试测试测试".getBytes();

		// 反复调用update输入数据:
		md.update(bytes);

		// 加密:查看内容
		byte[] result = md.digest();
		System.out.println(result.length);
		System.out.println(Arrays.toString(result));
	}
}

Demo02

java 复制代码
public class Demo02 {
	public static void main(String[] args) throws NoSuchAlgorithmException {
		// 获取消息摘要对象
		MessageDigest md5 = MessageDigest.getInstance("MD5");
		byte[] b = "我本将心照明月".getBytes();

		// 数据量小使用信息加密md5.digest(b)
//		byte[] result = md5.digest(b);
//		System.out.println("加密前:" + Arrays.toString(b));
//		System.out.println("加密后:" + Arrays.toString(result));
//		System.out.println("加密后长度:" + result.length);

		// 数据量大使用md5.update(b)加密
		md5.update(b);
		md5.update("爪爪吃蒸饺".getBytes());
		md5.update("蒸米粉肉".getBytes());
		
		//加密,查看内容
		byte[] result=md5.digest();
		
		System.out.println("加密前:" + Arrays.toString(b));
		System.out.println("加密后:" + Arrays.toString(result));
		System.out.println("加密后长度:" + result.length);
	}
}

Demo03

kava 复制代码
public class Demo03 {
	public static void main(String[] args) throws NoSuchAlgorithmException {
		String password = "wbjxxmy";
		byte[] bytepassword = password.getBytes();
		// 获取消息摘要对象
		MessageDigest md5 = MessageDigest.getInstance("MD5");
		// 反复添加需要加密的内容
		md5.update(bytepassword);
		// 加密
		byte[] digestpassword = md5.digest();

		System.out.println(Arrays.toString(bytepassword));
		System.out.println("加密后数组信息:" + Arrays.toString(digestpassword));
		System.out.println("加密后16进制字符串信息:" + byteToHex(digestpassword));
	}

	public static String byteToHex(byte[] digestpassword) {
		StringBuffer sb = new StringBuffer();
		for (byte b : digestpassword) {
			sb.append(String.format(",%02x", b));
		}
		return sb.toString();
	}
}

Demo04

md5算法对图片进行加密

java 复制代码
//md5算法对图片进行加密
public class Demo04 {
	public static void main(String[] args) throws NoSuchAlgorithmException, IOException {
		byte[] image = Files.readAllBytes(Paths.get("E:\\apesourcefile\\maomao.jpg"));

		MessageDigest md5 = MessageDigest.getInstance("MD5");

		md5.update(image);

		byte[] result = md5.digest();
		System.out.println(Arrays.toString(result));
		System.out.println(Demo03.byteToHex(result));
//		06203919a4288aafd95b78964386f0fd
//		06203919a4288aafd95b78964386f0fd
	}
}

5.SHA-1加密

SHA-1也是一种哈希算法,它的输出是160 bits,即20字节。SHA-1是由美国国家安全局开发的,SHA算法实际上是一个系列,包括SHA-0(已废弃)、SHA-1、SHA-256、SHA-512等。

在Java中使用SHA-1,和MD5完全一样,只需要把算法名称改为"SHA-1":

java 复制代码
import java.security.MessageDigest;

public class main {
	public static void main(String[] args)  {
		// 创建一个MessageDigest实例:
        MessageDigest md = MessageDigest.getInstance("SHA-1");
       
        // 反复调用update输入数据:
        md.update("Hello".getBytes("UTF-8"));
        md.update("World".getBytes("UTF-8"));
        
        // 20 bytes: db8ac1c259eb89d4a131b253bacfca5f319d54f2
        byte[] results = md.digest(); 

        StringBuilder sb = new StringBuilder();
        for(byte bite : results) {
        	sb.append(String.format("%02x", bite));
        }
        
        System.out.println(sb.toString());
	}
}

类似的,计算SHA-256,我们需要传入名称"SHA-256",计算SHA-512,我们需要传入名称"SHA-512"。Java标准库支持的所有哈希算法可以在这里https://docs.oracle.com/en/java/javase/14/docs/specs/security/standard-names.html#messagedigest-algorithms查到。

sha-1加密和md5加密

注意:md5加密长度为32位,sha-1加密长度为40位。

java 复制代码
public class Demo06 {
	public static void main(String[] args) throws NoSuchAlgorithmException {
		byte[] password = "爪爪吃蒸饺".getBytes();
		// md5加密
		String resultMd5 = HashUtlis.messageMD5(password);
		// sha-1加密
		String resultSha1 = HashUtlis.messageSHA1(password);
		System.out.println("md5加密后为" + resultMd5);
		System.out.println("Sha1加密后为" + resultSha1);
		char[] r1 = resultMd5.toCharArray();
		char[] r2 = resultSha1.toCharArray();
		System.out.println(r1.length);
		System.out.println(r2.length);
	}
}

小结:

哈希算法可用于验证数据完整性,具有防篡改检测的功能;

● 常用的哈希算法有MD5、SHA-1等;

● 用哈希存储口令时要考虑彩虹表攻击。

相关推荐
The Future is mine18 分钟前
Python计算经纬度两点之间距离
开发语言·python
Enti7c19 分钟前
HTML5和CSS3的一些特性
开发语言·css3
腥臭腐朽的日子熠熠生辉25 分钟前
解决maven失效问题(现象:maven中只有jdk的工具包,没有springboot的包)
java·spring boot·maven
爱吃巧克力的程序媛26 分钟前
在 Qt 创建项目时,Qt Quick Application (Compat) 和 Qt Quick Application
开发语言·qt
ejinxian27 分钟前
Spring AI Alibaba 快速开发生成式 Java AI 应用
java·人工智能·spring
杉之32 分钟前
SpringBlade 数据库字段的自动填充
java·笔记·学习·spring·tomcat
圈圈编码1 小时前
Spring Task 定时任务
java·前端·spring
俏布斯1 小时前
算法日常记录
java·算法·leetcode
独好紫罗兰1 小时前
洛谷题单3-P5719 【深基4.例3】分类平均-python-流程图重构
开发语言·python·算法
27669582921 小时前
美团民宿 mtgsig 小程序 mtgsig1.2 分析
java·python·小程序·美团·mtgsig·mtgsig1.2·美团民宿