哈希算法总结

概述

定义

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

特点

  • 哈希算法是不可逆的,不能反向推出明文。
  • 相同的输入一定会得到相同的输出。
  • 不同的输入大概率会得到不同的输出。

应用场景

  • 检测原始数据 软件、文件是否篡改;
  • 伪随机数生成;一次性口令;
  • 密码存储;
        Java字符串中的hashCode()就是一个哈希算法,它的输入是任意字符串,输出是固定的4字节int整数。
        两个相同字符串永远会计算出相同的hashCode,否则基于hashCode定位的HashMap就无法正常工作。这也是为什么当我们自定义一个class时,重写equals()方法时我们必须正确重写hashCode()方法。

重写equals()方法为什么必须重写hashCode()方法?
    equals方法用于判断两个对象是否相等,而hashCode方法用于计算对象的哈希值。在使用集合类(如HashSet、HashMap等)时,它们内部会用到hashCode方法来快速定位元素,然后再用equals方法来比较元素是否相等。
    只重写了equals方法而没有重写hashCode方法,它将使用默认的hashCode实现。这将导致两个逻辑上相等的对象计算出的哈希值不同,从而导致集合类无法正确地操作这些对象。
    为了保证集合类能够正常运作,必须重写hashCode方法,以保证逻辑上相等的对象计算出的哈希值相同。

哈希碰撞

    输入两个不同的值经过hash计算后,得到的hash值相同。
例如:

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

哈希碰撞是不可避免的,String的hashCode()把无限的输入集合映射到一个有限的输出集合,必然会产生碰撞。

常用哈希算法

哈希算法,根据碰撞概率,哈希算法的输出长度越长,就越难产生碰撞,也就越安全。

算法输出长度(位)输出长度(字节)
MD5128 bits16 bytes
SHA-1160 bits20 bytes
RipeMD-160160 bits20 bytes
SHA-256256 bits32 bytes
SHA-512512 bits64 bytes
Java标准库提供了常用的哈希算法,通过统一的接口进行调用。以MD5算法为例,看看如何计算哈希:
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;

public class Demo05 {
	public static void main(String[] args) {
		String s = "asdfasdf";
		try {
			//创建消息摘要算法对象(MD5算法)
			MessageDigest messageDigest = MessageDigest.getInstance("MD5");
			
			//添加原文内容(允许添加多次)
			messageDigest.update(s.getBytes());
			
			//消息摘要计算(执行哈希算法)
			byte[] data = messageDigest.digest();
			
			StringBuilder sb = new StringBuilder();
			
			for(byte b : data) {
				sb.append(String.format("%02x", b));
			}
			
			
			System.out.println("加密结果:"+ Arrays.toString(data));
			System.out.println("加密长度:"+data.length);
			System.out.println("密文字符:"+sb);
		} catch (NoSuchAlgorithmException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}

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

使用哈希算法存储用户密码

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

  • 数据库管理员能看到用户明文口令。
  • 数据库数据一旦泄露,黑客即可获取明文口令。

    使用MD5对用户口令进行加密,在用户输入原始口令后,系统计算用户输入的原始口令的MD5并与数据库存储的MD5对比,一致则正确。
    使用哈希口令时,会被彩虹表攻击,彩虹表是指一个预先计算好的常用口令和它们的MD5的对照表,这个表就是彩虹表。
    我们可以采取特殊措施来地狱彩虹表攻击:对每个口令额外添加随机数,这个方法称之为加盐(salt):

digest = md5(salt + inputPassword)

HmacMD5算法

HmacMD5 ≈ md5(secure_random_key, input)

    Hmac算法就是一种基于密钥的消息认证码算法,它的全称是Hash-based Message Authentication Code,是一种更安全的消息摘要算法。
因此,HmacMD5可以看作带有一个安全的key的MD5。使用HmacMD5而不是用MD5加salt,有如下好处:

  • HmacMD5使用的key长度是64字节,更安全;
  • Hmac是标准算法,同样适用于SHA-1等其他哈希算法;
  • Hmac输出和原有的哈希算法长度一致。

    可见,Hmac本质上就是把key混入摘要的算法。验证此哈希时,除了原始的输入数据,还要提供key。为了保证安全,我们不会自己指定key,而是通过Java标准库的KeyGenerator生成一个安全的随机的key。
    

public class Demo10 {
	public static void main(String[] args) {
		String password="123456789";
		try {
			//创建密钥生成器对象
			KeyGenerator keygen = KeyGenerator.getInstance("HmacMD5");
			
			//生成密钥
			SecretKey secretKey = keygen.generateKey();
			
			//获取秘钥原始内容(字节数组)
			byte[] keyArray = secretKey.getEncoded();
			
			//Base64
			String keyStr = Base64.getEncoder().encodeToString(keyArray);
			
			//基于密钥,进行MD5的消息摘要计算
			Mac mac = Mac.getInstance("HmacMD5");
			//初始化密钥(加入密钥)
			mac.init(secretKey);
			//添加密钥内容
			mac.update(password.getBytes());
			//最终计算
			byte[] ret = mac.doFinal();
			StringBuilder sb = new StringBuilder();
			for (byte b : ret) {
				sb.append(String.format("%02x", b));
			}
			System.out.println(keyArray.length);
			System.out.println(keyStr);
			System.out.println(ret.length);
			System.out.println(sb);
		} catch (Exception e) {
			// TODO: handle exception
		}
	}
}

和MD5相比,使用HmacMD5的步骤是:

  1. 通过名称HmacMD5获取KeyGenerator实例;
  2. 通过KeyGenerator创建一个SecretKey实例;
  3. 通过名称HmacMD5获取Mac实例;
  4. 用SecretKey初始化Mac实例;
  5. 对Mac实例反复调用update(byte[])输入数据;
  6. 调用Mac实例的doFinal()获取最终的哈希值。

在验证是否相同时,需要保存密钥的内容并使用SecretKey恢复。

public class Demo11 {
	public static void main(String[] args) {
		
		try {
			String keyStr ="S/cSmL2sMMRZZhxkMsdqk9hT/LzELj0sz74iQ4VqW4wIcCvPqTe/BnfcB229tI47+7sZNuCCnk3EHPXlf8z/5A==";
			String input = "123456789";
			//恢复秘钥
			byte[] keyArray = Base64.getDecoder().decode(keyStr);
			SecretKey key = new SecretKeySpec(keyArray, "HmacMd5");
			
			//计算
			Mac mac = Mac.getInstance("HmacMD5");
			mac.init(key);
			mac.update(input.getBytes());
			byte[] retArray = mac.doFinal();
			StringBuilder ret = new StringBuilder();
			for (byte b : retArray) {
				ret.append(String.format("%02x", b));
			}
			System.out.println(keyArray.length);
			System.out.println(keyStr);
			System.out.println(retArray.length);
			System.out.println(ret);
		} catch (NoSuchAlgorithmException | InvalidKeyException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}

  • 21
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值