哈希算法(Hash)之二

1. 概念

  • 哈希算法,也叫摘要算法(Digest)
  • 定义:对于任意长度的输入,得到固定长度的输出
  • 特点
    1. 相同的输入得到相同的输出
    2. 不同的输入尽最大可能得到不同的输出
    3. 我们不能根据已有的输出,猜测出其它输出
  • 哈希碰撞
    1. 不同的输入得到了相同的输出,当然我们需要尽可能地减少这种情况
    2. 理论上应该是输出长度越大,碰撞的概率会越小

2. 常用算法及长度

MD5 128 bits
SHA-1 160 bits
RipeMD-160 160 bits
SHA-256 256 bits
SHA-512 512 bits

3. 用途

  • 防止文件篡改,对文件进行hash算法后,得到了原始的哈希值,如果文件发生变化后,相应的hash值就会发生变化
  • 用作密码保存使用,原文密码经hash算法后,得到的内容和已保存的加密密码一致,则表示验证通过
    问题1:如果我们拿到了库中加密的密码,挨个去尝试,不是很容易就能破解一些密码吗?
    答: 是这样的,这个是基础暴力破解的方法,聪明一点的呢,可以使用彩虹表的方法,即使用一些常用的组合来生成相应的密码
    这样根据生成的密码,匹配一下已有的密码,很快就可以推导出原文
    问题2: 面对彩虹表的破解方法,我们有什么应对之策呢?
    答:答案也是有的,我们可以在哈希算法时,加入一些随机数,我们称之为盐salt,别人不知道这个salt时,也很难推出原文, 这样新的加密过程就是这样:
    newPassword = hash(password + salt)

4. 代码示例

  • 这里我们用JDK自带的API进行MD5加密
import java.security.MessageDigest;
MessageDigest md = MessageDigest.getInstance("MD5");
String input = "123456";
md.update(input.getBytes("UTF-8"));
byte[] result = md.digest();
System.out.println(new BigInteger(1,result).toString(16)); //按字节挨个输出16进制字符
//e10adc3949ba59abbe56e057f20f883e
  • SHA256哈希算法
    上面的代码主体都不动,只是将算法改为SHA-256
MessageDigest md = MessageDigest.getInstance("SHA-256");  //只是调整了算法名,最终我们得到的结果如下
//8d969eef6ecad3c29a3a629280e686cf0c3f5d5a86aff3ca12020c923adc6c92

有些哈希算法可能JDK自带的没有,这时我们可以引入第三方库,如bouncycastle
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>1.65</version>
</dependency>
这样,代码中添加一下算法,就可以体验其它哈希算法了,很棒!

Security.addProvider(new BouncyCastleProvider());
MessageDigest md = MessageDigest.getInstance("RipeMD160"); //同样是123456,用RipeMD160加密后就是如下的输出
//d8913df37b24c97f28f840114d05bd110dbb2e44

5. Hmac算法

  • 全称 Hash-based Message Authentication Code 基于密钥的消息认证码算法
  • 特点
    1. hmac的随机码,可以由java标准库生成,会更加安全
    2. hmac是标准算法,可以适用于SHA-256等各种hash算法
    3. 输出和原算法输出长度一致
    4. 当然,我们需要把生成的key,也一并保存下来
  • 示例
  1. 生成key
import javax.crypto.KeyGenerator;
KeyGenerator keyGen = KeyGenerator.getInstance("HmacMD5"); //生成KeyGenerator
SecretKey key = keyGen.generateKey(); //生成key
byte[] skeys = key.getEncoded(); //获得key的字节数组
String keyStr = new BigInteger(1,skeys).toString(16); //方便打印查看
System.out.println(keyStr);
//919bfbaa38011cb1e696b6c99ddb61fbd06036478d2be176503efd6e0ba120260c240f675663bfe24ad003533b8e284d0294b7f8c2e6696157cce66b3cfe367e
  1. 用hmacMD5加密
Mac mac = Mac.getInstance("HmacMD5");
mac.init(key); //使用前面生成key,初始化
mac.update("hello".getBytes());
byte[] resultData = mac.doFinal(); //加密,得到结果字节数组
String result = new BigInteger(1,resultData).toString(16); //加密后内容输出
System.out.println(result); 
//a28fbd6a086ce428dc26380a7aab1cc8
  1. 用hmacMD5对数据验证
SecretKey key1 = new SecretKeySpec(skeys,"HmacMD5"); //这里使用前面生成的key数组
Mac mac1 = Mac.getInstance("HmacMD5");
mac1.init(key1);
mac1.update("hello".getBytes());
byte[] resultData1 = mac1.doFinal(); //得到实际验证的加密数组
String result1 = new BigInteger(1,resultData1).toString(16);  //输出查看,当然和前面的输出是一样的
System.out.println(result1);
//a28fbd6a086ce428dc26380a7aab1cc8

注:验证的时候,我们一般需要从库中读取出keys值,用密码原文+keys值,计算出验证密文,方便和已保存的密文进行比较
遗留问题:库中一般保存的是16进制的字符,大家有没有好的工具方法,将16进制字符串,转为需要的字符数组,将2位16进制字符转为1个字节?
欢迎留言,谢谢!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容