哈希算法概述
哈希算法(Hash)又称摘要算法(Digest),它的作用是:对任意一组输入数据进行计算,得到一个固定长度的输出摘要。
在我们之前的学习中,也接触到了一些哈希算法的使用,比如String字符串的hashash()就是一个哈希算法,它的输入是任意字符串,输出是固定的4字节int整数。
哈希算法的特点及目的
哈希算法最重要的特点就是:
- 相同的输入一定得到相同的输出;
- 不同的输入大概率得到不同的输出。
所以,哈希算法的目的:为了验证原始数据是否被篡改。
什么是哈希碰撞
哈希碰撞是指:两个不同的输入得到了相同的输出。
例如我们熟知的通话和重地,它们的内容完全不同,却得到了相同的哈希值:
"通话".hashCode(); // 0x11ff03
"重地".hashCode(); // 0x11ff03
这就是哈希碰撞的一个体现,并且哈希碰撞是不能避免的,因为输出的字节长度是有限的,而输入数据的内容是无限的。就好比String的hashCode()为例,它的输出是4字节整数,最多只有4294967296种输出,但输入的数据长度是不固定的,有无数种输入,所以,哈希算法是把一个无限的输入集合映射到一个有限的输出集合,必然会产生碰撞。
既然哈希碰撞无法避免,那么就应该去想办法降低碰撞的概率,碰撞概率的高低还会直接关系到哈希算法的安全性,那么一个安全的哈希算法就应该满足:
- 碰撞概率低;
- 不能被反向猜测输出。
那么什么叫做不能被反向猜测输出?就好比以下这个例子:
hashA("java001") = "123456";
hashA("java002") = "123457";
hashA("java003") = "123458";
....
不难猜出,hashA(“java004”)的值会是"123459",这样的哈希算法是不安全的,一个合格的安全的哈希算法是不能被轻易找到某种规律反推输入的。
常用的哈希算法
哈希算法,根据碰撞概率,哈希算法的输出长度越长,就越难产生碰撞,也就越安全
1.MD5算法,输出字节长度:16bytes;
2.SHA算法系列,SHA-1输出字节长度:20bytes,SHA-256输出字节长度:32bytes, SHA-512输出字节长度:64bytes;
3.第三方开源库的RipeMD160算法,输出字节长度:20bytes。
三种常见哈希算法的封装(工具类)
代码如下(示例):
//hash算法(消息摘要算法)工具类
public class HashTools {
// 消息摘要对象
private static MessageDigest digest;
//私有化构造方法,该类不能被实例化,只可以调取静态方法
private HashTools() {}
// 通过消息摘要对象,处理加密内容
private static String handler(String source) {
digest.update(source.getBytes());//更新原始数据
byte[] bytes = digest.digest();//获取信息摘要(加密)
String hash = bytesToHex(bytes);//十六进制内容字符串
return hash;
}
// 按照MD5进行消息摘要计算(哈希计算)
public static String digestByMD5(String source) throws NoSuchAlgorithmException {
digest = MessageDigest.getInstance("MD5");
return handler(source);
}
// 按照RipeMD160进行消息摘要计算(哈希计算)
public static String digestByRipeMD160(String source) throws NoSuchAlgorithmException {
// 注册BouncyCastleProvider通知类
// 将提供的消息摘要算法注册到Security
Security.addProvider(new BouncyCastleProvider());
digest = MessageDigest.getInstance("RipeMD160");
return handler(source);
}
// 按照SHA-1进行消息摘要计算(哈希计算)
public static String digestBySHA1(String source) throws NoSuchAlgorithmException {
digest = MessageDigest.getInstance("SHA-1");
return handler(source);
}
// 按照SHA-256进行消息摘要计算(哈希计算)
public static String digestBySHA256(String source) throws NoSuchAlgorithmException {
digest = MessageDigest.getInstance("SHA-256");
return handler(source);
}
// 按照SHA-512进行消息摘要计算(哈希计算)
public static String digestBySHA512(String source) throws NoSuchAlgorithmException {
digest = MessageDigest.getInstance("SHA-512");
return handler(source);
}
// 将字节数组转换成16进制字符串
public static String bytesToHex(byte[] bytes) {
StringBuilder ret = new StringBuilder();
for (byte b : bytes) {
// 将字节值转换成2为十六进制字符串
ret.append(String.format("%02x", b));
}
return ret.toString();
}
}
彩虹表攻击与"加盐"
什么是彩虹表?
因为哈希算法相同的输入一定得到相同的输出,所以在某种程度上,如果有一个预先计算好的常用口令和它们的MD5的对照表,这个表就是彩虹表。如果用户使用了常用口令,黑客从MD5一下就能反查到相似原始口令,然后再进行暴力枚举,就可以很轻松的反推出加密前的内容。
那么如何抵御彩虹表呢?可以对每个口令额外添加随机值,这种方法称之为:加盐(salt)。
//通过随机加"盐",解决彩虹表问题
public class Salt{
public static void main(String[] args) throws NoSuchAlgorithmException {
//原始密码
String passWord="daxigua2024";
//产生随机盐值
String salt = UUID.randomUUID().toString().substring(0,4);
//创建基于SHA-1算法的消息摘要
MessageDigest sha1 = MessageDigest.getInstance("SHA-1");
sha1.update(passWord.getBytes());
sha1.update(salt.getBytes());
//计算加密结果
String digestHex = HashTools.bytesToHex(sha1.digest());
System.out.println(digestHex);
}
}
注意:因为加盐是对每个加密的内容中添加额外的随机数,以确保加密内容的更加安全,这个随机性带来更强的安全形的同时也带来了加密内容的随机性,随机到的不同的盐值,加密后的结果天差地别,所以,牢记==“盐值”==是之后对该加密内容进行验证的关键所在!
总结
- 哈希算法可用于验证数据完整性,具有防篡改检测的功能;
- 常用的哈希算法有MD5、SHA系列、RipeMD160等;
- 用哈希存储口令时要考虑彩虹表攻击。