哈希算法被广泛运用在各种途径中,本文章将带你初步认识哈希算法及它的加密的实现操作效果。
哈希算法又称摘要算法(Disgest),一般指SHA家族,它是安全散列算法(英语:Secure Hash Algorithm,缩写为SHA)是一个密码散列函数家族,是FIPS所认证的安全散列算法。能计算出一个数字消息所对应到的,长度固定的字符串(又称消息摘要)的算法。且若输入的消息不同,它们对应到不同字符串的机率很高。
System.out.println("Hello Java".hashCode());// 387417328
System.out.println("Hello java".hashCode());// 388370640
System.out.println("Hello,bob".hashCode());//-1095500357
哈希碰撞是指两个不同的输入得到了相同的输出。
例如:
String s1 = "通话";
String s2 = "重地";
System.out.println(s1.hashCode());// 1179395
System.out.println(s2.hashCode());// 1179395
哈希碰撞是不能避免的,这是因为输出的字节长度是固定的,但输入的字节长度不固定,有无数种输入,所以哈希算法是把无数的输入集合映射到一个有限的输出集合里,必然会产生碰撞。
既然不能避免碰撞,我们就要想办法降低碰撞的概率,提高哈希算法的安全性,所以一个安全的哈希算法必须满足:
算法 | 输出长度(位) | 输出长度(字节) |
---|---|---|
MD5 | 128bits | 16bytes |
SHA-1 | 160bits | 20bytes |
RipeMD-160 | 160bits | 20bytes |
SHA-256 | 256bits | 32bytes |
SHA-512 | 512bits | 64bytes |
为什么要对密码加密储存呢?设想如果数据库中直接存入用户的密码,数据库一旦泄露,那用户的信息将暴露无遗。所以将密码加密后存入数据库,将用户密码加密后的信息与存入数据库的信息去比对验证,这样就算数据库泄露,加密后的密码并不能被直接用于登录。
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
public class Main {
public static void main(String[] args) throws NoSuchAlgorithmException {
MessageDigest md5 = MessageDigest.getInstance("MD5");
md5.update("XXX123456".getBytes());
byte[] digestByte = md5.digest();
System.out.println(Arrays.toString(digestByte));
}
}
效果如图:
每次加密的长度,相同内容都加密的字节数组都是相同的。
由于加密成字节数组并不方便存储,我们往往会将他转换成十六进制的字符串来代替。为了方便之后的SHA-1,SHA-256等加密的实现,我们自己创建一个HashTool工具类。
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class HashTool {
private static MessageDigest digest;
private HashTool() {
super();
}
public static String digestMD5(String md5) throws NoSuchAlgorithmException {
digest = MessageDigest.getInstance("MD5");
digest.update(md5.getBytes());
return byteToHex(digest.digest());
}
public static String byteToHex(byte[] b) {
StringBuilder sb = new StringBuilder();
for (byte s : b) {
String format = String.format("%02x", s);
sb.append(format);
}
return sb.toString();
}
}
本类写了一个静态的byteToHex()方法将加密的字节数组,每一位字节对应两位十六进制数(不足两位用0补充,再将MD5加密的过程封装在digestMD5()方法中,最终调用byteToHex()方法将字节数组转换成十六进制的字符串。
效果如图:
同理,这些加密只需要在MessageDigest.getInstance(加密类型)方法中填入想要加密的类型即可,我们在HashTool类中实现,代码如下
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class HashTool {
private static MessageDigest digest;
private HashTool() {
super();
}
public static String digestMD5(String md5) throws NoSuchAlgorithmException {
digest = MessageDigest.getInstance("MD5");
digest.update(md5.getBytes());
return byteToHex(digest.digest());
}
public static String digestSHA256(String sha256) throws NoSuchAlgorithmException {
digest = MessageDigest.getInstance("SHA-256");
return handler(sha256);
}
public static String digestSHA512(String sha512) throws NoSuchAlgorithmException {
digest = MessageDigest.getInstance("SHA-512");
return handler(sha512);
}
public static String digestSHA1(String sha1) throws NoSuchAlgorithmException {
digest = MessageDigest.getInstance("SHA-1");
return handler(sha1);
}
private static String handler(String handle) {
digest.update(handle.getBytes());
return byteToHex(digest.digest());
}
public static String byteToHex(byte[] b) {
StringBuilder sb = new StringBuilder();
for (byte s : b) {
String format = String.format("%02x", s);
sb.append(format);
}
return sb.toString();
}
}
ripeMd160加密是第三方提供的加密包,想要使用ripeMd160加密必须导入bcprov-jdk15on-1.70.jar包。代码如下:
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.Security;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
public class BouncyDemo {
public static void main(String[] args) throws NoSuchAlgorithmException {
// 注册BouncyCastleProvider通知类
Security.addProvider(new BouncyCastleProvider());
MessageDigest ripeMd160 = MessageDigest.getInstance("RipeMD160");
ripeMd160.update("wbjxxmy".getBytes());
byte[] result = ripeMd160.digest();
System.out.println("RipeMD160加密后为:" + HashTool.byteToHex(result));
System.out.println("RipeMD160加密的字节长度为:" + ripeMd160.digest().length);
}
}
导入jar包后,想要让java程序知道你想使用RipeMD160加密,得先使用Seurity安全工具类的addProvider()方法,new一个BouncyCastleProvider来注册BouncyCastleProvider通知类,通知java程序将可能使用bcprov-jdk15on-1.70.jar包下的算法。
RipeMD160算法加密结果如图:
以上的加密真的就安全了吗?答案是否定的,我们还要防止彩虹表攻击。
彩虹表是一个用于加密散列函数逆运算的预先计算好的表, 为破解密码的散列值(或称哈希值、微缩图、摘要、指纹、哈希密文)而准备。
所以,为了预防彩虹表攻击,我们可以加盐(salt):对每个口令额外添加一个随机数,进行组合。
digest=md5(salt+inputpassword)
经过加盐处理的数据库表如下所示:
username | salt | password |
---|---|---|
张三 | H1r0a | 894152fff4f97e8fb2b8f304c5c56ebb |
李四 | 7$p2w | fc195df5b3ff11af8d07e10be912fb44 |
王五 | z5Sk9 | 1c8854242ab5c4a493a808538be5baf1 |
还有一种类似加盐,但比加盐更安全的加密方式,详情跳转至Hmac算法。
本文章简单介绍了哈希算法,并且实现了基本的常用的哈希算法对密码的加密。