1. 概念
- 哈希算法,也叫摘要算法(Digest)
- 定义:对于任意长度的输入,得到固定长度的输出
- 特点
- 相同的输入得到相同的输出
- 不同的输入尽最大可能得到不同的输出
- 我们不能根据已有的输出,猜测出其它输出
- 哈希碰撞
- 不同的输入得到了相同的输出,当然我们需要尽可能地减少这种情况
- 理论上应该是输出长度越大,碰撞的概率会越小
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 基于密钥的消息认证码算法
- 特点
- hmac的随机码,可以由java标准库生成,会更加安全
- hmac是标准算法,可以适用于SHA-256等各种hash算法
- 输出和原算法输出长度一致
- 当然,我们需要把生成的key,也一并保存下来
- 示例
- 生成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
- 用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
- 用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个字节?
欢迎留言,谢谢!
网友评论