目录
1.加密文字(以MD5为例,其他算法更改加密方法即可(除RipeMD-160)
一、概述
哈希算法(Hash)又称摘要算法(Digest),它的作用是:对任意一组输入数据进行计算,得到一个固定长度的输出摘要。
哈希算法最重要的特点就是:
● 相同的输入一定得到相同的输出;
● 不同的输入大概率得到不同的输出。
所以,哈希算法的目的:为了验证原始数据是否被篡改
二、哈希碰撞
哈希碰撞是指:两个不同的输入得到了相同的输出。例如:
"AaAaAa".hashCode(); // 0x7460e8c0
"BBAaBB".hashCode(); // 0x7460e8c0
"通话".hashCode(); // 0x11ff03
"重地".hashCode(); // 0x11ff03
碰撞无法避免,我们需要做的是减少碰撞发生的概率。一个安全的哈希算法必须满足:
● 碰撞概率低;
● 不能猜测输出。
三、常用哈希算法及其用途
哈希算法,根据碰撞概率,哈希算法的输出长度越长,就越难产生碰撞,也就越安全。
常用的哈希算法有:
算法 | 输出长度(位) | 输出长度(字节) |
MD5 | 128bits | 16bytes |
SHA-1 | 160bits | 20bytes |
RipeMD-160 | 160bits | 20bytes |
SHA-256 | 256bits | 32bytes |
SHA-512 | 512bits | 64bytes |
用途:
1.校验下载文件
因为相同的输入永远会得到相同的输出,因此,如果输入被修改了,得到的输出就会不同。我们在网站上下载软件的时候,经常看到下载页显示的MD5哈希值:
2.存储用户密码
哈希算法的另一个重要用途是存储用户口令。如果直接将用户的原始口令存放到数据库中,会产生极大的安全风险:
● 数据库管理员能够看到用户明文口令;
● 数据库数据一旦泄漏,黑客即可获取用户明文口令。
1.加密文字(以MD5为例,其他算法更改加密方法即可(除RipeMD-160)
使用MessageDigest抽象类时,首先根据哈希算法获取一个MessageDigest实例,然后,反复调用update(byte[])输入数据。当输入结束后,调用digest()方法获得byte[]数组表示的摘要,最后,把它转换为十六进制的字符串。代码示例如下:
//MD5加密算法
public class Demo6 {
public static void main(String[] args) {
try {
//创建基于MD5算法的消息摘要对象
MessageDigest md5 = MessageDigest.getInstance("MD5");
//更新原始数据
md5.update("今天星期一".getBytes());
md5.update("又是新的一天".getBytes());
//获取加密后的结果
byte[] digestBytes = md5.digest();
System.out.println("加密后(字节数组):" + Arrays.toString(digestBytes));
System.out.println("加密后(16进制字符串):" + HashTools.bytesToHex(digestBytes));
System.out.println("加密后长度:" + digestBytes.length);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
}
运行结果如下:
2.加密图片
首先,创建基于MD5加密算法的消息摘要对象,然后把图片读成字节数组,再利用MD5进行加密处理。
//按照MD5算法对图片进行加密
public class Demo7 {
public static void main(String[] args) {
try {
//图片的原始字节内容
byte[] imgBuf = Files.readAllBytes(Paths.get("C:\\Users\\Lenovo\\Pictures\\Screenshots\\1.png"));
//创建基于MD5算法的消息摘要对象
MessageDigest md5 = MessageDigest.getInstance("MD5");
//原始字节内容(图片)
md5.update(imgBuf);
byte[] imgDigest = md5.digest();
//获取加密摘要
System.out.println("加密后(字节数组):" + Arrays.toString(imgBuf));
System.out.println("加密后(16进制字符串:)" + HashTools.bytesToHex(imgDigest));
System.out.println("加密后长度:" + imgDigest.length); //MD5 算法的固定输出长度为16个字节
} catch (IOException e) {
throw new RuntimeException(e);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
}
3.防止彩虹表攻击
使用哈希口令时,还要注意防止彩虹表攻击。
什么是彩虹表呢?上面讲到了,如果只拿到MD5,从MD5反推明文口令,只能使用暴力穷举的方法。然而黑客并不笨,暴力穷举会消耗大量的算力和时间。但是,如果有一个预先计算好的常用口令和它们的MD5的对照表,这个表就是彩虹表。如果用户使用了常用口令,黑客从MD5一下就能反查到原始口令:
常用口令 | MD5 |
hello123 | f30aa7a662c728b7407c54ae6bfd27d1 |
12345678 | 25d55ad283aa400af464c76d713c07ad |
passw0rd | bed128365216c019988915ed3add75fb |
19700101 | 570da6d5277a646f6552b8832012f5dc |
wbjxxmy | 11d7a82f45f6a176fd9d5c100ccab40a |
当然,我们也可以采取特殊措施来抵御彩虹表攻击:对每个口令额外添加随机数,这个方法称之为加盐(salt):
digest = md5(salt + inputPassword)
//通过随机加盐解决彩虹表攻击问题
public class Demo8 {
public static void main(String[] args) {
try {
//创建基于MD5算法的消息摘要对象
MessageDigest md5 = MessageDigest.getInstance("MD5");
//原始密码
md5.update("wbjxxmy".getBytes());
//产生随机盐值
String salt = UUID.randomUUID().toString().substring(0,4);
md5.update(salt.getBytes());
//计算加密结果,MD5的输出结果为16个字节(32个字符)
String digest = HashTools.bytesToHex(md5.digest());
System.out.println("加密后:" + digest);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
}
四、RipeMD-160加密算法
1.RipeMD-160算法是第三方开源加密算法,使用之前需要导入bcprov-jdk15on-1.70.jar包,地址:bouncycastle.orghttps://www.bouncycastle.org/latest_releases.html
2.创建基于RipeMD-160算法的消息摘要对象之前,先注册BouncyCastleProvider通知类,将提供的消息摘要算法注册至Security。(其他与MD5加密算法相同)
//使用第三方开源库提供的RipeMD160消息摘要算法实现
public class Demo13 {
public static void main(String[] args) {
//注册BouncyCastleProvider通知类
//将提供的消息摘要算法注册至Security
Security.addProvider(new BouncyCastleProvider());
try {
//创建基于RipeMD160算法的消息摘要对象
MessageDigest ripeMD160 = MessageDigest.getInstance("RipeMD160");
//原始密码
ripeMD160.update("wbjxxmy".getBytes());
//获取消息摘要(加密)
byte[] result = ripeMD160.digest();
//消息摘要的字节长度和内容
System.out.println("加密结果(字节长度):" + result.length); //160位=20字节
System.out.println("加密结果(字节内容):" + Arrays.toString(result));
//16进制内容字符串
String hex = new BigInteger(1,result).toString(16);
System.out.println("加密结果(字符串长度):" + hex.length());
System.out.println("加密结果(字符串内容):" + hex);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
}
五、使用Hash算法工具类进行加密(多次调用时减少重复)
在每次进行加密时,会重复调用MessageDigest()方法创建对象,更新加密信息,调用digest()方法加密,所以可以把重复的部分写入HashTools工具类当中。
//Hash算法(消息摘要算法)工具类
public class HashTools {
//消息摘要对象
private static MessageDigest digest;
//构造方法私有
private HashTools(){}
//按照MD5进行消息摘要计算(哈希计算)
public static String digestMD5(String source) throws NoSuchAlgorithmException {
digest = MessageDigest.getInstance("MD5");
return handler(source);
}
//按照SHA-1进行消息摘要计算(哈希计算)
public static String digestSHA1(String source) throws NoSuchAlgorithmException {
digest = MessageDigest.getInstance("SHA-1");
return handler(source);
}
//通过消息摘要对象,处理加密内容
private static String handler(String source){
digest.update(source.getBytes());
byte[] bytes = digest.digest();
String hash = bytesToHex(bytes);
return hash;
}
//将字节值转换为2位十六进制字符串
public static String bytesToHex(byte[] bytes){
StringBuilder ret = new StringBuilder();
for (byte b:bytes
) {
ret.append(String.format("%02x",b)); //转换成16进制的两位字节形式
}
return ret.toString();
}
}
调用HashTools工具类,完成加密操作:
//通过自定义工具类,完成对应加密处理
public class Demo9 {
public static void main(String[] args) {
try {
//MD5加密
String MD5 = HashTools.digestMD5("wbjxxmy");
System.out.println("MD5=" + MD5);
//SHA-1加密
String SHA1 = HashTools.digestSHA1("wbjxxmy");
System.out.println("SHA1=" + SHA1);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
}
}
总结:
● 哈希算法可用于验证数据完整性,具有防篡改检测的功能;
● 常用的哈希算法有MD5、SHA-1等;
● 用哈希存储口令时要考虑彩虹表攻击。