哈希算法又被称为摘要算法,它的作用是:对任意一组输入数据进行计算,得到一个固定长度的输出摘要
哈希算法的目的:为了验证原始数据是否被篡改。
哈希算法最重要的特点是:
相同的输入一定得到相同的输出
不同的输入大概率得到不同的输出
目录
(2)SHA-1加密/SHA-256加密/SHA-512加密
那么为什么两个不同的输入大概率得到不同的输出,难道两个不同的输入还会得到相同的输出吗? 答案是肯定的,我们把这种两个不同的输入得到了相同的输出就称为:哈希碰撞
一、哈希碰撞
两个不同的输入得到了相同的输出
java字符串中的hashCode()就是计算哈希值的算法,在计算中我们会发现有两个不一样的字符串计算出的哈希值是相同的例如:
如上图所示,像这种输入数据不同输出结果却相同的现象我们就称为哈希碰撞。
那么哈希碰撞可以避免吗?
答案是不能,碰撞是一定会出现的,因为输出的字节长度是固定(即输出的可能是固定的)的但是输入的数据的长度却是不固定的(即有无数种输入),所以,哈希算法是把一个无限的输入集合映射到一个有限的输出集合,必然会产生碰撞。
但是我们也不必害怕碰撞,一个安全的哈希算法必须要满足:
1、碰撞概率低
2、不能猜测输出
二、常用的哈希算法
算法 | 输出长度(位) | 输出长度(字节) |
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 |
根据碰撞概率,哈希算法的输出长度越长,就越难产生碰撞,也就越安全。
(1)MD5加密
第一步:创建基于MD5算法的信息摘要对象(括号内是所用算法)
MessageDigest md5=MessageDigest.getInstance("MD5");
第二步:更新原始数据
md5.update("天王盖地虎小鸡炖蘑菇".getBytes());
第三步:获取加密后的结果
byte[] a=md5.digest();
第四步:输出加密后的结果
System.out.println("加密后的结果"+Arrays.toString(a));
第五步:输出加密后的结果(16进制字符串)
StringBuilder a2=new StringBuilder(); for(byte b:a) { // 将字节值转换为2位十六进制字符串 a2.append(String.format("%02x", b)); } System.out.println("加密后的结果(16进制字符串)"+a2.toString());
第五步:输出加密后结果的长度(无论输入的数据长度为多少,输出的结果的长度都为16)
System.out.println("长度为"+a.length);
(2)SHA-1加密/SHA-256加密/SHA-512加密
在Java中使用SHA-1、SHA-256、SHA-512和MD5的方法一样,只需要把算法名称改为“SHA-1”、“SHA-256”、“SHA-512”,例如:
MessageDigest md5=MessageDigest.getInstance("SHA-1");
(3)RipeMD-160加密
需要先导入如下jar包:
import java.security.NoSuchAlgorithmException;
import java.security.Security;
import java.util.Arrays;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.pqc.jcajce.provider.BouncyCastlePQCProvider;
//使用第三方开源库提供的RipeMD160消息摘要算法实现
public class RipeMD160 {
public static void main(String[] args) throws NoSuchAlgorithmException {
// 注册BouncyCastleProvider通知类
// 将提供的消息摘要算法注册至Security
Security.addProvider(new BouncyCastleProvider());
// 获取RipeMD160算法的“消息摘要对象”(加密对象)
MessageDigest ripeMd160=MessageDigest.getInstance("RipeMD160");
// 更新原始数据
ripeMd160.update("wbjxxmy".getBytes());
// 获取信息摘要(加密
byte[] result=ripeMd160.digest();
System.out.println("加密结果(字节长度)"+result.length);
System.out.println("加密结果(字节内容)"+Arrays.toString(result));
String hex=new BigInteger(1,result).toString(16);
System.out.println("加密结果(字符串长度):"+hex.length());
System.out.println("加密结果(字符串内容"+hex);
}
}
三、哈希算法的用途
(1)校验下载文件
我们在网站上下载软件的时候,经常看到下载页显示的MD5哈希值(这个就是用来判断下载的是否是原始的,未经篡改的文件)
有的时候我们不是从官网下载的软件就会发现里面被植入了很多第三方的插件,根本就不是官方原版的,那么我们如何确保我们下载到本地的文件和人家官方原版的就是一样的呢?
我们只需要将本地的文件进行MD5的加密,然后和官网上显示的MD5加密结果进行对比,如果一样则下载的就和人家官网的文件是一样的,如果不一样,则证明这个文件是被人篡改过的。
(2)存储用户密码
在很多软件都有用户名和密码,这些用户名和密码就存储在服务器端的数据库中,如果这些数据库中的用户名和密码是以明文方式来存储的话,就会带来一些安全隐患,别人很容易就可以拿到用户名和密码,为了规避之一隐患,我们就可以将数据库中存的用户名与密码进行MD5加密,再进行存储,当需要登录时,只需要将用户输入的用户名和密码进行MD5加密,再与数据库中的加密结果进行对比,如果一样则证明正确。
这样数据库中存储的都是加密后的结果,即使黑客拿到也没有意义,
但是也可能会出现彩虹表攻击
即黑客可能会把常用的一些密码的MD5加密后的结果存到数据字典中,形成彩虹表字典,只要MD5相同就可以反推出原始密码是多少,这就也形成了安全隐患
我们也可以采取特殊措施来抵御彩虹表攻击,对每个口令额外添加随机数,这个方法称之为加盐:
就像我们在一杯水里面加盐,加1g盐还是2g盐的味道是不一样的,这个方法也是,我们额外随机加入值,那MD5加密后的结果就不一定了
例如可以加上四位随机数再与密码一起进行MD5加密:
String password="12389578";
// 盐
String salt=UUID.randomUUID().toString().substring(0, 4);
MessageDigest md5=MessageDigest.getInstance("MD5");
md5.update(password.getBytes());
md5.update(salt.getBytes());
String p=HashTool.HX(md5.digest());
System.out.println(p);
System.out.println(p.length());