哈希算法(Hash)又称摘要算法(Digest)
作用是:对任意一组输入数据进行计算,得到一个固定长度的输出摘要。
哈希算法最重要的特点就是:
1.相同的输入一定得到相同的输出
2.不同的输入大概率得到不同的输出(可能会出现哈希碰撞,即不同输出得到相同输出)
哈希算法可以验证原始数据是否被篡改,所以可以用来校验下载文件,另外还可以用来存储用户密码。
但因为相同的输入通过哈希算法会得到相同的结果,所以简单的密码和口令在数据泄露容易遭到彩虹表攻击(彩虹表即常用口令和他们MD5的对照表),数据泄露时常用口令很容易通过MD5被反查到。
当然我们可以对要进行哈希算法的口令额外增加随机数,这个方法称为“加盐”,可以有效增加密码和口令的安全性。
常用的哈希算法
算法 |
输出长度(位) |
输出长度(字节) |
MD5 |
128bits |
16bytes |
SHA-1 |
160bits |
20bytes |
RipeMD-160 |
160bits |
20bytes |
SHA-256 |
256bits |
32bytes |
SHA-512 |
512bits |
64bytes |
以MD5算法为例,在用MD5进行编码时,首先需要根据编码方式创建MessageDigest实例,然后调用update(byte[ ])更新原始数据,再通过digest()方法获得byte[ ]形式的加密后的结果 ,最后转化为十六进制的字符串。
具体代码如下:
package com.gjh.demo02;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class Text02 {
public static void main(String[] args) {
try {
String password="wbjxxmynhmyzgq123";
//获取基于MDS加密算法的工具对象
MessageDigest digest=MessageDigest.getInstance("MD5");
//更新原始数据
digest.update(password.getBytes());
//获取加密后的结果
byte[] resultByteArray=digest.digest();
System.out.println(resultByteArray);
System.out.println(resultByteArray.length);
StringBuilder result1=new StringBuilder();
for(byte resultByte:resultByteArray) {
result1.append(String.format("%02x", resultByte));
}
System.out.println(result1);
System.out.println(result1.length());
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
}
}
可以看到以下是它的实现结果:

由于转化为十六进制的字符串时,我们设置一个字节占2个字符,所以得到的字符串长度为32个字符
SHA-1算法的编码方式与MD5完全一致,只需要在创建MessageDigest实例时将编码方式改为"SHA-1"
以下为MD5和SHA-1算法结果长度的对比的代码:
package com.gjh.demo02;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
public class Text04 {
public static void main(String[] args) {
String password="wxshfg";
String salt="acsvd";
try {
MessageDigest digest=MessageDigest.getInstance("SHA-1");
MessageDigest digest1=MessageDigest.getInstance("MD5");
digest.update(password.getBytes());
digest.update(salt.getBytes());
digest1.update(password.getBytes());
digest1.update(salt.getBytes());
byte[] result=digest.digest();
byte[] result1=digest1.digest();
System.out.println("使用SHA-1的byte数组result值");
System.out.println(result);
System.out.println(result.length);
System.out.println("使用MD5的byte数组result值");
System.out.println(result1);
System.out.println(result1.length);
StringBuilder sb=new StringBuilder();
for(byte b:result) {
sb.append(String.format("%02x", b));
}
StringBuilder sb1=new StringBuilder();
for(byte b1:result1) {
sb1.append(String.format("%02x", b1));
}
System.out.println("使用SHA-1的StringBuilder值");
System.out.println(sb);
System.out.println(sb.length());
System.out.println("使用MD5的StringBuilder值");
System.out.println(sb1);
System.out.println(sb1.length());
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
}
}
可以看到以下是它的实现结果:

Hmac算法
Hmac算法是一种基于密钥的消息认证码算法,是一种更安全的消息摘要算法。
Hmac算法总是和某种哈希算法配合起来用的。例如,我们使用MD5算法,对应的就是Hmac MD5算法,它相当于“加盐”的MD5。
HmacMD5与MD5相比:
HmacMD5使用的key长度是64字节,更安全;
Hmac是标准算法,同样适用于SHA-1等其他哈希算法;
Hmac输出和原有的哈希算法长度一致。
在用HmacMD5进行编码时,首先需要根据编码方式创建KeyGenerator实例,KeyGenerator实例再调用generateKey()方法获取秘钥key,通过key.getEncoded()获取秘钥key的字节数组,再将其转化为十六进制的字符串。
在加密过程,需要通过名称HmacMD5获取Mac实例,对Mac实例调用update(byte[ ])输入要进行加密的数据,再调用Mac实例的doFinal()获得最终的哈希值,再将其转化为十六进制的字符串。
具体代码如下:
package com.gjh.demo03;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import javax.crypto.KeyGenerator;
import javax.crypto.Mac;
import javax.crypto.SecretKey;
public class Text01 {
public static void main(String[] args) {
String password="nhmyzgq";
try {
//1.生成秘钥
//2.秘钥生成器keyGenerator
KeyGenerator keyGe=KeyGenerator.getInstance("HmacMD5");
//生成秘钥
SecretKey key=keyGe.generateKey();
//获取秘钥key的字节数组(64)
byte[] ketByteArray=key.getEncoded();
System.out.println(ketByteArray);
System.out.println("秘钥的长度"+ketByteArray.length+"字节");
StringBuilder sb=new StringBuilder();
for(byte b:ketByteArray) {
sb.append(String.format("%02x", b));
}
System.out.println("秘钥的内容"+sb);
System.out.println(sb.length());
Mac mac=Mac.getInstance("HmacMD5");
mac.init(key);
//更新原始内容
mac.update(password.getBytes());
//加密
byte[] resultByteArray=mac.doFinal();
System.out.println("加密结果"+resultByteArray.length+"字节");
StringBuilder resultStr=new StringBuilder();
for(byte by:resultByteArray) {
resultStr.append(String.format("%02x", by));
}
System.out.println("加密结果:"+resultStr);
System.out.println("加密结果长度:"+resultStr.length());
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
}
}
}
可以看到以下是它的实现结果:

除了通过Hmac计算的秘钥,也可以通过指定的字节数组恢复秘钥,恢复秘钥的语句就是new SecretKeySpec(hkey, "HmacMD5")。
具体代码如下:
package com.gjh.demo03;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
public class Text02 {
public static void main(String[] args) {
String password="nhmyzgq";
try {
byte[] keyByteArray= {126, 49, 110, 126, -79, -5, 66, 34, -122, 123, 107, -63, 106, 100, -28, 67, 19, 23, 1, 23, 47, 63, 47, 109, 123, -111, -27, -121, 103, -11, 106, -26, 110, -27, 107, 40, 19, -8, 57, 20, -46, -98, -82, 102, -104, 96, 87, -16, 93, -107, 25, -56, -113, 12, -49, 96, 6, -78, -31, -17, 100, 19, -61, -58};
SecretKey key=new SecretKeySpec(keyByteArray ,"HmacMD5");
Mac mac=Mac.getInstance("HmacMd5");
mac.init(key);
mac.update(password.getBytes());
byte[] resultByteArray=mac.doFinal();
StringBuilder resultStr=new StringBuilder();
for(byte b:resultByteArray) {
resultStr.append(String.format("%02x", b));
}
System.out.println("加密结果"+resultStr);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (InvalidKeyException e) {
e.printStackTrace();
}
}
}
BouncyCastle
BouncyCastle是一个提供了很多哈希算法和加密算法的第三方开源库。它提供了很多Java标准库没有提供的哈希算法和加密算法,例如,RipeMD160哈希算法。
与MD5算法不同的是,我们要使用BouncyCastle提供的RipeMD160算法,需要先注册BouncyCastle提供的通知类对象BouncyCastleProvider
具体代码如下:
package com.gjh.demo;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.Security;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.pqc.jcajce.provider.BouncyCastlePQCProvider;
public class Text01 {
public static void main(String[] args) {
try {
//注册BouncyCastle提供的通知类对象BouncyCastleProvider
Security.addProvider(new BouncyCastleProvider());
//获取RipeMD160算法的“消息摘要对象”(加密对象)
MessageDigest msg=MessageDigest.getInstance("RipeMD160");
//更新原始数据
msg.update("hello".getBytes());
//获取消息摘要(加密)
byte[] result=msg.digest();
//消息摘要的字节长度和内容
System.out.println(result); //160位=20字节
System.out.println(result.length);
//16进制内容字符串
String hex=new BigInteger(1,result).toString(); //20字节,40个字符,按一个字节占2个字符
System.out.println(hex);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
}
}