目录
哈希算法概述
哈希算法(Hash)又称摘要算法(Digest),它的作用是:对任意一组输入数据进行计算,得到一个固定长度的输出摘要。哈希算法的目的:为了验证原始数据是否被篡改。
哈希算法最重要的特点就是:
●相同的输入一定得到相同的输出;
●不同的输入大概率得到不同的输出。
常用哈希算法
常用的哈希算法有:
输出长度(位) | 输出长度(字节) | |
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 |
根据碰撞概率,哈希算法的输出长度越长,就越难产生碰撞,也就越安全。
哈希算法的用途
校验下载文件
我们在网站上下载软件的时候,经常看到下载页显示的MD5哈希值:
如何判断下载到本地的软件是原始的、未经篡改的文件?我们只需要自己计算一下本地文件的哈希值,再与官网公开的哈希值对比,如果相同,说明文件下载正确,否则,说明文件已被篡改。
存储用户密码
哈希算法的另一个重要用途是存储用户口令。如果直接将用户的原始口令存放到数据库中,会产生极大的安全风险:
●数据库管理员能够看到用户明文口令;
●数据库数据一旦泄漏,黑客即可获取用户明文口令。
不存储用户的原始口令,那么如何对用户进行认证?方法是存储用户口令的哈希,例如,MD5。在用户输入原始口令后,系统计算用户输入的原始口令的MD5并与数据库存储的MD5对比,如果一致,说明口令正确,否则,口令错误。
用哈希口令时,还要注意防止彩虹表攻击。什么是彩虹表呢?上面讲到了,如果只拿到MD5,从MD5反推明文口令,只能使用暴力穷举的方法。然而黑客并不笨,暴力穷举会消耗大量的算力和时间。但是,如果有一个预先计算好的常用口令和它们的MD5的对照表,这个表就是彩虹表。如果用户使用了常用口令,黑客从MD5一下就能反查到原始口令。
当然,我们也可以采取特殊措施来抵御彩虹表攻击:对每个口令额外添加随机数,这个方法称之为加盐(salt):
digest = md5(salt + inputPassword)
常用哈希算法使用总结
MD5:
1.使用MessageDigest时,我们首先根据哈希算法获取一个MessageDigest实例。
2.反复调用update(byte[])输入数据。
3.当输入结束后,调用digest()方法获得byte[]数组表示的摘要。
4.最后,把它转换为十六进制的字符串。
import java.security.MessageDigest;
public class main {
public static void main(String[] args) {
// 创建一个MessageDigest实例:
MessageDigest md = MessageDigest.getInstance("MD5");
// 反复调用update输入数据:
md.update("Hello".getBytes("UTF-8"));
md.update("World".getBytes("UTF-8"));
// 16 bytes: 68e109f0f40ca72a15e05cc22786f8e6
byte[] results = md.digest();
StringBuilder sb = new StringBuilder();
for(byte bite : results) {
sb.append(String.format("%02x", bite));
}
System.out.println(sb.toString());
}
}
运行上述代码,可以得到输入HelloWorld的MD5是68e109f0f40ca72a15e05cc22786f8e6。
SHA-1
SHA-1也是一种哈希算法,它的输出是160 bits,即20字节。SHA-1也是一种哈希算法,它的输出是160 bits,即20字节。
import java.security.MessageDigest;
public class main {
public static void main(String[] args) {
// 创建一个MessageDigest实例:
MessageDigest md = MessageDigest.getInstance("SHA-1");
// 反复调用update输入数据:
md.update("Hello".getBytes("UTF-8"));
md.update("World".getBytes("UTF-8"));
// 20 bytes: db8ac1c259eb89d4a131b253bacfca5f319d54f2
byte[] results = md.digest();
StringBuilder sb = new StringBuilder();
for(byte bite : results) {
sb.append(String.format("%02x", bite));
}
System.out.println(sb.toString());
}
}
类似的,计算SHA-256,我们需要传入名称"SHA-256",计算SHA-512,我们需要传入名称"SHA-512"。
Hmac算法
Hmac算法就是一种基于密钥的消息认证码算法,是一种标准的基于密钥的哈希算法,可以配合MD5、SHA-1等哈希算法,计算的摘要长度和原摘要算法长度相同,是一种更安全的消息摘要算法。Hmac算法总是和某种哈希算法配合起来用的。例如,我们使用MD5算法,对应的就是Hmac MD5算法,它相当于“加盐”的MD5:HmacMD5 ≈ md5(secure_random_key, input)
和MD5相比,使用HmacMD5的步骤是:
1通过名称HmacMD5获取KeyGenerator实例;
2通过KeyGenerator创建一个SecretKey实例;
3通过名称HmacMD5获取Mac实例;
4用SecretKey初始化Mac实例;
5对Mac实例反复调用update(byte[])输入数据;
6调用Mac实例的doFinal()获取最终的哈希值。
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import javax.crypto.KeyGenerator;
import javax.crypto.Mac;
import javax.crypto.SecretKey;
public class main {
public static void main(String[] args) throws NoSuchAlgorithmException, IllegalStateException, UnsupportedEncodingException, InvalidKeyException {
// 获取HmacMD5秘钥生成器
KeyGenerator keyGen = KeyGenerator.getInstance("HmacMD5");
// 产生秘钥
SecretKey secreKey = keyGen.generateKey();
// 打印随机生成的秘钥:
byte[] keyArray = secreKey.getEncoded();
StringBuilder key = new StringBuilder();
for(byte bite:keyArray) {
key.append(String.format("%02x", bite));
}
System.out.println(key);
// 使用HmacMD5加密
Mac mac = Mac.getInstance("HmacMD5");
mac.init(secreKey); // 初始化秘钥
mac.update("HelloWorld".getBytes("UTF-8"));
byte[] resultArray = mac.doFinal();
StringBuilder result = new StringBuilder();
for(byte bite:resultArray) {
result.append(String.format("%02x", bite));
}
System.out.println(result);
}
}
可见,Hmac本质上就是把key混入摘要的算法。验证此哈希时,除了原始的输入数据,还要提供key。为了保证安全,我们不会自己指定key,而是通过Java标准库的KeyGenerator生成一个安全的随机的key。