目录
一、概述
在前面讲到哈希算法时,我们说,存储用户的哈希口令时,要加盐存储,目的就在于抵御彩虹表攻击。我们回顾一下哈希算法:
digest = hash(input)
正是因为相同的输入会产生相同的输出,我们加盐的目的就在于,使得输入有所变化:
digest = hash(salt + input)
这个salt可以看作是一个额外的“认证码”,同样的输入,不同的认证码,会产生不同的输出。因此,要验证输出的哈希,必须同时提供“认证码”。
Hmac算法就是一种基于密钥的消息认证码算法,它的全称是Hash-based Message Authentication Code,是一种更安全的消息摘要算法。
Hmac算法总是和某种哈希算法配合起来用的。例如,我们使用MD5算法,对应的就是Hmac MD5算法,它相当于“加盐”的MD5:HmacMD5 ≈ md5(secure_random_key, input)
因此,HmacMD5可以看作带有一个安全的key的MD5。使用HmacMD5而不是用MD5加salt,有如下好处:
● HmacMD5使用的key长度是64字节,更安全;
● Hmac是标准算法,同样适用于SHA-1等其他哈希算法;
● Hmac输出和原有的哈希算法长度一致。
可见,Hmac本质上就是把key混入摘要的算法。验证此哈希时,除了原始的输入数据,还要提供key。为了保证安全,我们不会自己指定key,而是通过Java标准库的KeyGenerator生成一个安全的随机的key。
二、实现
1.HmacMD5加密:
步骤:
1.产生密钥(通过KeyGenerator类获取一个实例,使用SecretKey接口生成密钥)
2.使用密钥,进行加密(使用Mac类获取HmacMD5加密算法对象)
①初始化
②更新原始加密内容
③加密处理
代码如下:
//HmacMD5算法
public class Demo10 {
public static void main(String[] args) {
String password = "wbjxxmy";
try {
//1.产生密钥
//获取HmacMD5密钥生成器
KeyGenerator keyGen = KeyGenerator.getInstance("HmacMd5");
//生成密钥
SecretKey secretKey = keyGen.generateKey();
System.out.println("密钥:" + Arrays.toString(secretKey.getEncoded()));
System.out.println("密钥长度(64字节):" + secretKey.getEncoded().length);
System.out.println("密钥:" + HashTools.bytesToHex(secretKey.getEncoded()));
//2.使用密钥,进行加密
//获取HmacMD5加密算法对象
Mac mac = Mac.getInstance("HmacMD5");
mac.init(secretKey); //初始化密钥
mac.update(password.getBytes()); //更新原始加密内容
byte[] bytes = mac.doFinal(); //加密处理,并获取加密结果
String ret1 = HashTools.bytesToHex(bytes);
System.out.println("加密结果16进制字符串:" + ret1);
System.out.println("加密结果(16位字节)长度:" + bytes.length);
System.out.println("加密结果(32位字符)长度:" + ret1.length());
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
} catch (InvalidKeyException e) {
throw new RuntimeException(e);
}
}
}
2.按照字节数组恢复密钥
//按照字节数组恢复密钥
public class Demo11 {
public static void main(String[] args) {
//原始密码
String password = "wbjxxmy";
//密钥(字节数组)
byte[] bytes = {-71, -53, 43, 52, -89, 117, 96, 34, -95, 2, 94, -11, 85, 63, -27, 104, 32, 79, 36, -45, 62, -7, 111, -92, -113, -110, 90, 3, -56, -93, -95, -68, -28, -119, -34, 102, 99, 61, 121, 75, -18, -96, -82, -73, -86, -91, -96, 70, -85, 6, -8, -42, -105, 83, 88, -2, -106, -118, 126, -98, 60, -57, -77, 7};
try {
//创建HmacMD5加密算法对象
Mac mac = Mac.getInstance("HmacMd5");
//恢复密钥(字节数组)
SecretKey key = new SecretKeySpec(bytes,"HmacMd5");
mac.init(key);
mac.update(password.getBytes());
String hash = HashTools.bytesToHex(mac.doFinal());
//fb7db9f386fb985d97fc3e2abbdb2598
System.out.println("字节数组验证:" + hash);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
} catch (InvalidKeyException e) {
throw new RuntimeException(e);
}
}
}
3.按照字符串恢复密钥
//按照字符串恢复密钥
public class Demo12 {
public static void main(String[] args) {
String password = "wbjxxmy";
//密钥(字符串)
String keystr = "b9cb2b34a7756022a1025ef5553fe568204f24d33ef96fa48f925a03c8a3a1bce489de66633d794beea0aeb7aaa5a046ab06f8d6975358fe968a7e9e3cc7b307";
//用于保存密钥:密钥长度为64字节
byte[] bytes = new byte[64];
for (int i = 0,k = 0;i < keystr.length();i += 2,k++){
String s = keystr.substring(i,i + 2);
bytes[k] = (byte) Integer.parseInt(s,16); //转换成16进制
}
try {
//获取Hmac加密算法对象
Mac mac = Mac.getInstance("HmacMD5");
SecretKey key = new SecretKeySpec(bytes,"HmacMD5");
mac.init(key); //初始化密钥
mac.update(password.getBytes()); //加密处理,并获取加密结果
String hash = HashTools.bytesToHex(mac.doFinal()); //加密结果处理成16进制字符串
System.out.println("字符串验证:" + hash);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
} catch (InvalidKeyException e) {
throw new RuntimeException(e);
}
}
}
总结:
Hmac算法是一种标准的基于密钥的哈希算法,可以配合MD5、SHA-1等哈希算法,计算的摘要长度和原摘要算法长度相同。