哈希算法
哈希算法(Hash)又称摘要算法(Digest),它的作用是:对任意一组输入数据进行计算,得到一个固定长度的输出摘要。
哈希算法最重要的特点就是:
- 相同的输入一定得到相同的输出;
- 不同的输入大概率得到不同的输出。
哈希算法的目的就是为了验证原始数据是否被篡改。
Java字符串的hashCode()
就是一个哈希算法,它的输入是任意字符串,输出是固定的4字节int
整数:
"hello".hashCode(); // 0x5e918d2 "hello, java".hashCode(); // 0x7a9d88e8 "hello, bob".hashCode(); // 0xa0dbae2f
哈希碰撞
哈希碰撞是指,两个不同的输入得到了相同的输出:
"AaAaAa".hashCode(); // 0x7460e8c0 "BBAaBB".hashCode(); // 0x7460e8c0
常用的哈希算法有:
算法 | 输出长度(位) | 输出长度(字节) |
---|---|---|
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
我们以MD5算法为例,看看如何对输入计算哈希:
import java.math.BigInteger; import java.security.MessageDigest; public class Main { public static void main(String[] args) throws Exception { // 创建一个MessageDigest实例: MessageDigest md = MessageDigest.getInstance("MD5"); // 反复调用update输入数据: md.update("Hello".getBytes("UTF-8")); md.update("World".getBytes("UTF-8")); byte[] result = md.digest(); // 16 bytes: 68e109f0f40ca72a15e05cc22786f8e6 System.out.println(new BigInteger(1, result).toString(16)); } }
使用MessageDigest
时,我们首先根据哈希算法获取一个MessageDigest
实例,然后,反复调用update(byte[])
输入数据。当输入结束后,调用digest()
方法获得byte[]数组表示的摘要,最后,把它转换为十六进制的字符串。
哈希算法的用途
因为相同的输入永远会得到相同的输出,因此,如果输入被修改了,得到的输出就会不同。
例子:
1、对比文件与原始文件一致性
2、存储用户口令(密码)
想要拿到用户的原始口令,必须用暴力穷举的方法,一个口令一个口令地试,直到某个口令计算的MD5恰好等于指定值。
还要注意防止彩虹表(预先计算好的常用口令和它们的MD5的对照表)攻击。
采取措施来抵御彩虹表攻击,方法是对每个口令额外添加随机数,这个方法称之为加盐(salt):
digest = md5(salt+inputPassword)
加盐的目的在于使黑客的彩虹表失效,即使用户使用常用口令,也无法从MD5反推原始口令。
SHA-1
SHA-1也是一种哈希算法,它的输出是160 bits,即20字节。SHA-1是由美国国家安全局开发的,SHA算法实际上是一个系列,包括SHA-0(已废弃)、SHA-1、SHA-256、SHA-512等。
在Java中使用SHA-1,和MD5完全一样,只需要把算法名称改为"SHA-1"
:
import java.math.BigInteger; import java.security.MessageDigest; public class Main { public static void main(String[] args) throws Exception { // 创建一个MessageDigest实例: MessageDigest md = MessageDigest.getInstance("SHA-1"); // 反复调用update输入数据: md.update("Hello".getBytes("UTF-8")); md.update("World".getBytes("UTF-8")); byte[] result = md.digest(); // 20 bytes: db8ac1c259eb89d4a131b253bacfca5f319d54f2 System.out.println(new BigInteger(1, result).toString(16)); } }
类似的,计算SHA-256,我们需要传入名称"SHA-256"
,计算SHA-512,我们需要传入名称"SHA-512"
。Java标准库支持的所有哈希算法可以在这里查到。