Java Hmac加密算法

Hmac算法

1.概述

在前面讲到哈希算法时,我们说,存储用户的哈希口令时,要加盐存储,目的就在于抵御彩虹表攻击。我们回顾一下哈希算法: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。
下面是使用HmacMD5的参考代码:

import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;

import javax.crypto.KeyGenerator;
import javax.crypto.Mac;
import javax.crypto.SecretKey;

public class main {
	public static void main(String[] args) throws NoSuchAlgorithmException, IllegalStateException, UnsupportedEncodingException, InvalidKeyException {
        
        // 获取HmacMD5秘钥生成器
		KeyGenerator keyGen = KeyGenerator.getInstance("HmacMD5");
		
        // 产生秘钥
        SecretKey secreKey = keyGen.generateKey();
        
        // 打印随机生成的秘钥:
        byte[] keyArray = secreKey.getEncoded();
        StringBuilder key = new StringBuilder();
        for(byte bite:keyArray) {
        	key.append(String.format("%02x", bite));
        }
        System.out.println(key);
        
        // 使用HmacMD5加密
        Mac mac = Mac.getInstance("HmacMD5");
        mac.init(secreKey); // 初始化秘钥
        mac.update("HelloWorld".getBytes("UTF-8"));
        byte[] resultArray = mac.doFinal();
        
        StringBuilder result = new StringBuilder();
        for(byte bite:resultArray) {
        	result.append(String.format("%02x", bite));
        }
        System.out.println(result);
	}
}

2.Hmac加密算法的步骤

和MD5相比,使用HmacMD5的步骤是:
1.通过名称HmacMD5获取KeyGenerator实例;
2.通过KeyGenerator创建一个SecretKey实例;
3.通过名称HmacMD5获取Mac实例;
4.用SecretKey初始化Mac实例;
5.对Mac实例反复调用update(byte[])输入数据;
6.调用Mac实例的doFinal()获取最终的哈希值。

例如这个例子:

加密1
public class Demo07 {
	public static void main(String[] args) throws NoSuchAlgorithmException, InvalidKeyException {
		// 1.通过名称HmacMD5获取KeyGenerator实例;
		KeyGenerator key = KeyGenerator.getInstance("HmacMD5");
		// 2.通过KeyGenerator创建一个SecretKey实例;
		SecretKey secretKey = key.generateKey();
		byte[] bytes = secretKey.getEncoded();
		System.out.println("密钥:" + Arrays.toString(bytes));
		System.out.println("密钥长度:" + bytes.length);

		String keytoString = HashUtlis.byteToHex(bytes);
		System.out.println("密钥对应的字符串" + keytoString);
		System.out.println("密钥对应的字符串长度" + keytoString.length());
		// 3.通过名称HmacMD5获取Mac实例;
		Mac mac = Mac.getInstance("HmacMD5");
		// 4.用SecretKey初始化Mac实例;
		mac.init(secretKey);
		// 5.对Mac实例反复调用update(byte[])输入数据;
		mac.update("爪爪吃蒸饺".getBytes());
		// 6.调用Mac实例的doFinal()获取最终的哈希值。[加密]
		byte[] macByte = mac.doFinal();
		System.out.println("===================");
		System.out.println("加密内容:" + Arrays.toString(macByte));
		System.out.println("加密长度:" + macByte.length);

		String result = HashUtlis.byteToHex(macByte);
		System.out.println("加密对应的字符串" + result);
		System.out.println("加密对应的字符串长度" + result.length());

	}
}

输出结果为:

密钥:[-114, 11, -34, 107, -39, -122, -84, -92, 59, 89, 11, 85, -3, 56, 25, -104, 111, -128, -48, 112, 52, -83, 125, -29, 96, -20, 118, 2, 106, -62, 33, 47, -111, -14, 55, 45, -67, -26, -23, 84, -37, -23, 24, 16, 78, 119, -23, 25, 101, -31, 107, 114, 69, -37, 1, -125, 7, 74, 40, 35, 96, 12, -86, 95]
密钥长度:64
密钥对应的字符串8e0bde6bd986aca43b590b55fd3819986f80d07034ad7de360ec76026ac2212f91f2372dbde6e954dbe918104e77e91965e16b7245db0183074a2823600caa5f
密钥对应的字符串长度128
===================
加密内容:[89, -73, 42, 124, -109, 87, -22, 43, 105, -70, -82, 16, -52, -63, -105, -27]
加密长度:16
加密对应的字符串59b72a7c9357ea2b69baae10ccc197e5
加密对应的字符串长度32
加密2
public class Demo08 {
	public static void main(String[] args) throws NoSuchAlgorithmException, InvalidKeyException {
		byte[] password = "爪爪吃蒸饺".getBytes();

		// 1.准备密钥信息
//		byte[] byteKey = {-114, 11, -34, 107, -39, -122, -84, -92, 59, 89, 11, 85, -3, 56, 25, -104, 111, -128, -48, 112, 52, -83, 125, -29, 96, -20, 118, 2, 106, -62, 33, 47, -111, -14, 55, 45, -67, -26, -23, 84, -37, -23, 24, 16, 78, 119, -23, 25, 101, -31, 107, 114, 69, -37, 1, -125, 7, 74, 40, 35, 96, 12, -86, 95};
		String str = "8e0bde6bd986aca43b590b55fd3819986f80d07034ad7de360ec76026ac2212f91f2372dbde6e954dbe918104e77e91965e16b7245db0183074a2823600caa5f";
		byte[] byteKey = HashUtlis.hexToByte(str);

		// 2.恢复密钥对象
		// 参数1:密钥的字节数组
		// 参数2:密钥的算法类型
		SecretKey key = new SecretKeySpec(byteKey, "HmacMD5");
		Mac mac = Mac.getInstance("HmacMD5");
		// 添加密钥,将需要加密的内容添加进去
		mac.init(key);
		mac.update("爪爪吃蒸饺".getBytes());
		// 加密
		byte[] macByte = mac.doFinal();
		System.out.println("===================");
		System.out.println("加密内容:" + Arrays.toString(macByte));
		System.out.println("加密长度:" + macByte.length);

		String result = HashUtlis.byteToHex(macByte);
		System.out.println("加密对应的字符串" + result);
		System.out.println("加密对应的字符串长度" + result.length());
	}
}
加密解密的案例
public class Demo09 {
	public static void main(String[] args) throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException{
		// 明文信息--要加密的信息
		String message = "长江长江我是黄河";
		byte[] key = "1234567890diguaa".getBytes();
		byte[] data = encodeMessage(key, message.getBytes());
		System.out.println("加密后的内容" + Arrays.toString(data));
		byte[] deMessage = decodeMessage(key, data);
		System.out.println("解密后的内容" + new String(deMessage));
	}

	// 加密
	private static byte[] encodeMessage(byte[] key, byte[] message) throws NoSuchAlgorithmException,
			NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
		// 对称加密对象,需要传入算法/工作模式/填充模式
		Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
		// 密钥准备
		SecretKey secretKey = new SecretKeySpec(key, "AES");
		// 加密对象进行初始化操作
		cipher.init(Cipher.ENCRYPT_MODE, secretKey);
		// 加密
		byte[] data = cipher.doFinal(message);
		return data;
	}

	// 解密
	public static byte[] decodeMessage(byte[] key, byte[] data) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
		// 对称加密对象,需要传入算法/工作模式/填充模式
		Cipher cipher = Cipher.getInstance("AES/ECB/PKCS5Padding");
		// 密钥准备
		SecretKey secretKey = new SecretKeySpec(key, "AES");
		// 加密对象进行初始化操作
		cipher.init(Cipher.DECRYPT_MODE, secretKey);
		// 加密
		byte[] messag = cipher.doFinal(data);
		return messag;
	}
}

我们可以用Hmac算法取代原有的自定义的加盐算法,因此,存储用户名和口令的数据库结构如下:

在这里插入图片描述

有了Hmac计算的哈希和SecretKey,我们想要验证怎么办?这时,SecretKey不能从KeyGenerator生成,而是从一个byte[]数组恢复:

// 原始密码
String password = "nhmyzgq";

// 通过"秘钥的字节数组",恢复秘钥
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);

3.小结

Hmac算法是一种标准的基于密钥的哈希算法,可以配合MD5、SHA-1等哈希算法,计算的摘要长度和原摘要算法长度相同。

  • 23
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值