1、概述
哈希算法又称位摘要算法,它的作用是”对任意一致输入数据进行计算,得到一个固定长度的输出摘要。
哈希算法的特点:
1、相同的输入一定得到相同的输出;
2、不同的输入大概率得到不同的输出。
哈希算法的目的是为了验证原始数据是否被篡改
2、哈希碰撞
哈希碰撞指的是两个不同的输入得到了相同的输出;
哈希碰撞无法避免,是一定会出现的,因为输出的字节长度是固定的。但是输入的数据长度是不固定的,有无数种输入,所以哈希算法是把一个无限的输入集合映射到一个有限的输出集合,必然会发生碰撞。
所以,一个安全的哈希算法必须满足:
1、碰撞概率低;
2、不能猜测输出:输入的任意一个bit的变化会造成输出完全不同,这样就很难从输出反推输入。
3、常用的哈希算法
哈希算法,根据碰撞概率,哈希算法的输出长度越长,就越难产生碰撞,也就越安全。
算法 | 输出长度(位) | 输出长度(字节) |
---|---|---|
MD5 | 128 bits | 16 |
SHA-1 | 160 bits | 20 |
RipeMD-160 | 160 bits | 20 |
SHA-256 | 256 bits | 32 |
SHA-512 | 512 bits | 64 |
1、MD5加密算法
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
//MD5加密
public class Test06 {
public static void main(String[] args) {
try {
// 创建基于MD5的消息摘要对象
MessageDigest md5 = MessageDigest.getInstance("MD5");
// 更新原始数据 输入数据
md5.update("叮叮车".getBytes());
// 获取加密后的结果
byte[] res = md5.digest();
System.out.println("加密后的结果:" + Arrays.toString(res));
System.out.println("十六进制字符串:" + HashTools.BytesToHex(res));
System.out.println("加密结果的长度:" + res.length);
System.out.println();
// 更新原始数据 输入数据
md5.update("叮叮".getBytes());
md5.update("车".getBytes());
// 获取加密后的结果
byte[] ress = md5.digest();
System.out.println("加密后的结果:" + Arrays.toString(ress));
System.out.println("十六进制字符串:" + HashTools.BytesToHex(ress));
System.out.println("加密结果的长度:" + ress.length);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
}
}
首先我们需要创建一个基于MD5加密算法的摘要对象MessageDigest,然后使用update方法更新原始数据,在使用md5.digest()获取加密后的结果。从上面的代码我们可以看到,该结果是一个byte类型的数组,并且该数组的长度固定位16,不会改变。然后我们提供一个HashTools的工具类,将获取的结构转换为十六进制的格式进行输出。
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class HashTools {
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("SHA-1");
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);
}
public static String handler(String source) {
digest.update(source.getBytes());
byte[] buf = digest.digest();
String hex = BytesToHex(buf);
return hex;
}
// 将自己数组转换为十六进制字符串
public static String BytesToHex(byte[] buff) {
StringBuilder res = new StringBuilder();
for (byte b : buff) {
res.append(String.format("%02x", b));
}
return res.toString();
}
}
我们还可以使用MD5算法对图片进行加密
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
// 图片加密
public class Test07 {
public static void main(String[] args) throws IOException, NoSuchAlgorithmException {
byte[] imageBytes = Files.readAllBytes(Paths.get("D:\\java\\ddc.jpg"));
MessageDigest md5 = MessageDigest.getInstance("md5");
md5.update(imageBytes);
// 获取加密摘要
byte[] res = md5.digest();
System.out.println(Arrays.toString(res));
System.out.println(HashTools.BytesToHex(res));
System.out.println(res.length);// MD5算法输出的长度固定为16个字节
}
}
对图片加密与对字符串加密的大致步骤是一样的,对图片的加密需要我们以流的方式获取图片,最后我们发现加密后的结果还是16位。
对数据进行加密的作用主要是为了防止数据泄露,为了避免彩虹表的攻击,我们可以采取特殊措施来抵御彩虹表的攻击,这个方法位加盐(salt)
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.UUID;
public class Test08 {
public static void main(String[] args) throws NoSuchAlgorithmException {
String password = "ddcwxhn";
// 加上盐值 随机
String salt = UUID.randomUUID().toString().substring(0, 4);
// 产生md5算法
MessageDigest md5 = MessageDigest.getInstance("MD5");
md5.update(password.getBytes());
md5.update(salt.getBytes());
byte[] ret = md5.digest();
String res = HashTools.BytesToHex(ret);
System.out.println(res);
}
}
加盐其实就是在更新原始数据的时候需要加上一个你自己设置的值 同时让这个值参与加密,使我们最后的结果是无法对照的,需要注意的是,我们必须要记录这个盐值,否则将无法找回。
2、SHA加密算法
SHA加密算法的使用和MD5是一样的,只需要将MD5改为SHA-1就可以。
import java.security.NoSuchAlgorithmException;
public class Test09 {
public static void main(String[] args) throws NoSuchAlgorithmException {
String password = "ddcloveyou";
String md5 = HashTools.digestByMD5(password);
String SHA1 = HashTools.digestBySHA1(password);
String SHA256 = HashTools.digestBySHA256(password);
String SHA512 = HashTools.digestBySHA512(password);
System.out.println(md5);
System.out.println(SHA1);
System.out.println(SHA256);
System.out.println(SHA512);
}
}
3、RipeMD-160加密算法
这是一个由第三方开源库提供的RipeMD160消息摘要算法实现,需要注册BouncyCastleProvider通知类,将提供的消息摘要算法注册至Security,然后就可以使用。
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.Security;
import java.util.Arrays;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
// 使用第三方开源库提供的RipeMD160消息摘要算法实现
public class Demo03 {
public static void main(String[] args) throws NoSuchAlgorithmException {
// 注册BouncyCastleProvider通知类
// 将提供的消息摘要算法注册至Security
Security.addProvider(new BouncyCastleProvider());
MessageDigest md = MessageDigest.getInstance("RipeMD160");
md.update("ddcloveyou".getBytes());
byte[] res = md.digest();
System.out.println(Arrays.toString(res));
System.out.println(HashTools.BytesToHex(res));
System.out.println(res.length);
}
}
使用该算法需要导入jar包:bcprov-jdk15on-1.70.jar
4、Hmac算法
Hmac算法是一种基于密钥的消息认证码算法,它的全称是Hash-based Message Authentication Code,是一种更安全的消息摘要算法,Hmac算法总是和某种哈希算法配合起来使用,例如HmacMD5算法,所以HmacMD5算法可以看作带有一个安全的key的MD5算法。
1、HmacDM5使用的可以长度位64字节,更安全;
2、Hmac是标准算法,同样适合于SHA-1等其他哈希算法;
3、Hmac输出和原有的哈希算法长度一致。
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import javax.crypto.KeyGenerator;
import javax.crypto.Mac;
import javax.crypto.SecretKey;
//Hmac算法
public class Demo01 {
public static void main(String[] args) throws NoSuchAlgorithmException, InvalidKeyException {
String password = "ddcloveyou";
// 获取HmacDM5密钥生成器
KeyGenerator keyGen = KeyGenerator.getInstance("HmacMD5");
// 生成密钥
SecretKey key = keyGen.generateKey();
System.out.println(Arrays.toString(key.getEncoded()));
System.out.println(key.getEncoded().length);
System.out.println(HashTools.BytesToHex(key.getEncoded()));
// 使用密钥 进行加密
// 获取Mac加密算法对象
Mac mac = Mac.getInstance("HmacMD5");
mac.init(key);// 初始化密钥
mac.update(password.getBytes());// 更新原始密钥
byte[] res = mac.doFinal();// 加密处理 获取加密结果
System.out.println("加密后的:" + HashTools.BytesToHex(res));
System.out.println(res.length);
}
}
使用Hmac算法我们需要使用KeyGenerator获取密钥生成器,然后使用SecretKey生成密钥,然后使用密钥进行加密,获取Mac加密算法对象,然后需要先使用init方法初始化密钥,然后同样使用update方法来更新原始密钥,最后使用doFinal方法进行加密处理,获取加密结果,结构同样是一个字节数组。
当我们需要验证的时候,由两种方式恢复密钥
1、 按照字节数组恢复
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
// 按照字节数组恢复密钥
public class Demo02 {
public static void main(String[] args) throws NoSuchAlgorithmException, InvalidKeyException {
String password = "ddcloveyou";
byte[] buf = { -53, 109, 14, 88, -85, 42, 116, 121, -78, 115, -103, -84, 72, 61, -51, 102, 40, -5, -38, -103,
-116, 67, -5, 39, 92, -54, -100, -12, -20, 31, -1, 18, 34, 71, -96, -38, 25, 49, -48, -76, 98, -126,
-106, -37, 31, 70, 20, -117, -55, -48, 67, 65, 97, -46, -15, -92, -78, 8, -108, -43, -94, 85, -120,
125 };
// 恢复密钥
SecretKey key = new SecretKeySpec(buf, "HmacMD5");
Mac mac = Mac.getInstance("HmacMD5");
mac.init(key);
mac.update(password.getBytes());
String res = HashTools.BytesToHex(mac.doFinal());
System.out.println(res);
// 67bf43d45007ab92eeae124c981bff53
}
}
2、按照密钥(字符串)恢复
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
// 按照字节数组恢复密钥
public class Demo02 {
public static void main(String[] args) throws NoSuchAlgorithmException, InvalidKeyException {
// 按照密钥
String str = "81dd6754fc06525ed648bf188a355e5b5691c3839558ee33a9de4e01c0c19de9e5d8b4f6b606c882d87ea3be93cb7755d6d95697666f0b86372cca1d4058ce5b";
// 用于保存密钥
byte[] keyByte = new byte[64];
for (int i = 0, k = 0; i < str.length(); i += 2, k++) {
String s = str.substring(i, i + 2);
keyByte[k] = (byte) Integer.parseInt(s, 16);
}
// 恢复密钥
SecretKey key = new SecretKeySpec(keyByte, "HmacMD5");
Mac mac = Mac.getInstance("HmacMD5");
mac.init(key);
mac.update(password.getBytes());
String res = HashTools.BytesToHex(mac.doFinal());
System.out.println(res);
// 67bf43d45007ab92eeae124c981bff53
}
}
以上就是比较基础的哈希算法