目录
一、相关概念
1、什么是哈希算法
2、什么是哈希碰撞
3、哈希算法的用途
(1)校验下载的文件
编辑
(2)存储用户密码
二、常用的哈希算法
1、MD5
(1)对字符串进行加密
(2)对图片信息进行加密
2、SHA-1
3、RipeMD160
4、小结
三、总结
哈希算法(Hash)又称摘要算法(Digest),它的作用是:对任意一组输入数据进行计算,得到一个固定长度的输出摘要。
哈希算法最重要的特点就是:
●相同的输入一定得到相同的输出;
●不同的输入大概率得到不同的输出。
所以,哈希算法的目的:为了验证原始数据是否被篡改。
注:Java字符串的hashCode()就是一个哈希算法,它的输入是任意字符串,输出是固定的4字节int整数:
为什么不同的输入是大概率得到不同的输出,因为产生的哈希碰撞,
即两个不同的输入得到了相同的输出。例如:
"通话".hashCode(); // 0x11ff03
"重地".hashCode(); // 0x11ff03
那么碰撞能不能避免?答案是不能。碰撞是一定会出现的,因为输出的字节长度是固定的,String的hashCode()输出是4字节整数,最多只有4294967296种输出,但输入的数据长度是不固定的,有无数种输入。所以,哈希算法是把一个无限的输入集合映射到一个有限的输出集合,必然会产生碰撞。
碰撞不可怕,我们担心的不是碰撞,而是碰撞的概率,因为碰撞概率的高低关系到哈希算法的安全性。一个安全的哈希算法必须满足:
●碰撞概率低;
●不能猜测输出。
注:哈希算法,根据碰撞概率,哈希算法的输出长度越长,就越难产生碰撞,也就越安全。
因为相同的输入永远会得到相同的输出,因此,如果输入被修改了,得到的输出就会不同。我们在网站上下载软件的时候,经常看到下载页显示的MD5哈希值:
通过对比MD5的哈希值就可以进行效验确认是否为同一文件。
在日常使用中,如果直接将用户的原始口令存放到数据库中,会产生极大的安全风险:
●数据库管理员能够看到用户明文口令;
●数据库数据一旦泄漏,黑客即可获取用户明文口令。
不存储原始密码的情况如何进行验证呢?例如,MD5。在用户输入原始口令后,系统计算用户输入的原始口令的MD5并与数据库存储的MD5对比,如果一致,说明密码正确,否则,密码错误。这样一来,数据库管理员看不到用户的原始口令。即使数据库泄漏,黑客也无法拿到用户的原始口令。
算法 | MD5 | SHA-1 | RipeMD160 | SHA-256 | SHA-512 |
输出长度(字节) | 16 | 20 | 20 | 32 | 64 |
他的输出长度(字节):16 bytes
public static void main(String[] args) throws NoSuchAlgorithmException {
//创建基于MD5算法的消息摘要对象
MessageDigest md5 = MessageDigest.getInstance("MD5");
//更新原始数据
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);
//原始内容相同,结果相同
}
输出结果:
这里我们可以看到,处理过是使用byte字节数组存储的,这里我使用了自己定义的工具类HashTools中的bytesToHex()方法进行了转换成了16进制的字符串。
//将字节数组转换为16进制字符串
public static String bytesToHex(byte[] bytes) {
StringBuilder ret = new StringBuilder();
for(byte b : bytes) {
//将字节值转换为2位十进制字符串(不足2位补0)
ret.append(String .format("%02x", b));
}
return ret.toString();
}
public static void main(String[] args) throws IOException, NoSuchAlgorithmException {
//获取图片信息
byte[] buff = Files.readAllBytes(Paths.get("C:\\Users\\GAN\\Desktop\\1\\谦谦.jpg"));
//创建加密算法对象
MessageDigest md5 = MessageDigest.getInstance("MD5");
//原始数据更新
md5.update(buff);
//获取加密摘要
byte[] digestBytes = md5.digest();
System.out.println("加密后结果(字符数组):" + Arrays.toString(digestBytes));
System.out.println("加密后结果(16进制字符串):" + HashTools.bytesToHex(digestBytes));
System.out.println("加密结果长度:" + digestBytes.length);
}
这里我偷懒了,没用try,catch处理异常,直接抛出来。
我们可以看到无论是多大的信息,最后加密长度都是16个字节。
是哈希算法的输出固定长度摘要的特点。
他的输出长度(字节):20 bytes
public static void main(String[] args) throws NoSuchAlgorithmException {
//创建基于MD5算法的消息摘要对象
MessageDigest md5 = MessageDigest.getInstance("SHA-1");
//更新原始数据
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);
//原始内容相同,结果相同
}
他的输出长度(字节):20 bytes
public static void main(String[] args) throws NoSuchAlgorithmException {
//注册BouncyCastleProvider通知类
//将提供的消息摘要算法注册至Security
Security.addProvider(new BouncyCastleProvider());
//获取RipeMD160算法的消息摘要对象
MessageDigest ripeMd160 = MessageDigest.getInstance("RipeMD160");
//更新原始数据
ripeMd160.update("我自横刀向天笑".getBytes());
//获取消息摘要
byte[] result = ripeMd160.digest();
//消息摘要的字节长度与内容
System.out.println("加密结果长度:" + result.length);
System.out.println("加密结果内容:" + Arrays.toString(result));
}
输出结果:
RipeMD160与上述算法不同,不是java基础库中自带的算法,所以需要导入第三方库bcprov-jdk15on-1.70.jarb,且在使用时需要进行安全注册。
在上述的几种算法中我们可以看到,在实现加密的代码中,步骤完全相同,所以我们为了方便可以进行一部分的提取,进行代码复用,这里我写入了工具类中。
//消息摘要对象
private static MessageDigest digest;
private HashTools() {
}
//MD5加密方法
public static String digestByMD5(String source) throws NoSuchAlgorithmException {
digest =MessageDigest.getInstance("MD5");
return handler(source);
}
//SHA-1加密方法
public static String digestBySHA1(String source) throws NoSuchAlgorithmException {
digest =MessageDigest.getInstance("SHA1");
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);
}
//处理方法
private static String handler(String source) {
digest.update(source.getBytes());
byte[] bytes = digest.digest();
String hash = bytesToHex(bytes);
return hash;
}
简单应用:
public static void main(String[] args) {
try {
//MD5加密
String md5 = HashTools.digestByMD5("wzhdxtx");
//SHA-1加密
String sha1 = HashTools.digestBySHA1("wzhdxtx");
//SHA-256
String sha2 = HashTools.digestBySHA256("wzhdxtx");
//SHA-512
String sha3 = HashTools.digestBySHA512("wzhdxtx");
System.out.println("MD5加密:" + md5);
System.out.println("SHA-1加密:" + sha1);
System.out.println("SHA-256加密:" + sha2);
System.out.println("SHA-512加密:" + sha3);
} catch (NoSuchAlgorithmException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
哈希算法(Hash)又称摘要算法(Digest),它的作用是:对任意一组输入数据进行计算,得到一个固定长度的输出摘要。
常用的哈希算法有MD5(16),SHA-1(20)、RipeMD160(20)、SHA-256(32)、SHA-512(64)。
加密步骤: