MAC 算法
# MAC 算法
MAC 算法,可以理解为是一种带密钥的 Hash 算法
# Hash 算法的缺点
考虑以下场景:
- 我们需要发送一段消息,先通过 Hash 算法计算出 Hash 值(也叫摘要值),然后一起发送给接收方;
- 接收方收到消息后,对消息进行计算,也得到一个哈希值,然后两个摘要值比较,如果一样,则说明数据是对的。
乍一看,好像上述的示例是没什么问题的,但是一旦遇到“中间人攻击”:
直接将你消息和摘要都改了,你是无法知道数据在传输过程被篡改了。
一句话:Hash 算法只可以验证数据的完整性,在传输过程中数据没有被别人篡改过部分内容,或者因网络问题丢失部分数据;但是无法保证传输过程中,整体的数据有没有篡改。
# MAC 算法
MAC 算法,全称 Message Authentication Codes,也是一种消息摘要算法,也叫消息认证码算法。这种算法的核心是基于秘钥的散列函数。可以简单这样理解,MAC 算法是 MD5 算法和 SHA 算法的升级版,是在这两种算法的基础上,又加入了秘钥的概念,所以会更加安全。
密钥就好比是我们之前讲的盐,然后通过哈希算法,对密钥 + 消息 计算出摘要值
严格来说,MAC 有很多实现方式,比较常见的是基于哈希算法的 MAC。
使用 MAC 算法后,数据校验的过程:
发送方根据消息和密钥,生成 MAC 值;然后发送消息和 MAC 值。
接收方收到消息和 MAC 值,也根据消息和密钥算出 MAC 值,然后比较。
如果中间有人修改消息,那么计算出来的 MAC 值是不同的,因此保护了消息的完整性和真实性(没有被人篡改过)
MAC 密钥通常是共享的秘密密钥,只有消息的发送方和接收方知道。保护 MAC key 的安全性非常重要,一旦泄露,攻击者可以使用它来伪造或篡改消息,从而破坏消息的完整性和可信性。
至于 MAC 密钥怎么传输,我们暂且不表,先知道 MAC 算法是什么。
# MAC 算法的种类
MAC 算法是含有密钥的散列算法的总称,就好比哈希算法分为 MD 系列和 SHA 系列等。MAC 算法也是依赖具体某个哈希算法的,并在其基础上添加了密钥支持,因此也被称为 HMAC 算法,可以划分为:
- MD 分支:Hmac-MD2、Hmac-MD4、Hmac-MD5。
- SHA 分支:Hmac-SHA1、Hmac-SHA256、Hmac-SHA512、Hmac-SHA224 等
MAC 算法应用于很多场景,如 Linux 客户端的 SecureCRT,Google 身份验证器、银联 pos 机终端等,都可以看到 MAC 算法的影子,现在已经成为事实上的 Internet 安全标准。
# HMAC 算法
Hmac,全称 Hash-based Message Authentication Code,是一种更安全的消息摘要算法。
HmacMD5 可以看作带有一个安全的 key(盐)的 MD5。使用 HmacMD5 而不是用 MD5 加 salt,有如下好处:
- HmacMD5 使用的 key 长度是 64 字节,更安全;
- Hmac 是标准算法,同样适用于 SHA-1 等其他哈希算法;
- Hmac 输出和原有的哈希算法长度一致。
可见,Hmac 本质上就是把 key 混入摘要的算法。验证此哈希时,除了原始的输入数据,还要提供 key。
为了保证安全,我们不会自己指定 key,而是通过 Java 标准库的 KeyGenerator 生成一个安全的随机的 key。下面是使用 HmacMD5 的代码:
package chapter12Hash;
import javax.crypto.KeyGenerator;
import javax.crypto.Mac;
import javax.crypto.SecretKey;
import java.math.BigInteger;
public class Hash5HMAC {
public static void main(String[] args) throws Exception{
KeyGenerator keygen = KeyGenerator.getInstance("HmacMD5");
SecretKey key = keygen.generateKey(); //随机生成一个key
Mac mac = Mac.getInstance("HmacMD5");
mac.init(key);
mac.update("HelloWorld".getBytes("UTF-8"));
byte[] result = mac.doFinal();
System.out.println(new BigInteger(1, result).toString(16));
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
和 MD5 相比,使用 HmacMD5 的步骤是:
- 通过名称
HmacMD5
获取KeyGenerator
实例; - 通过
KeyGenerator
创建一个SecretKey
实例; - 通过名称
HmacMD5
获取Mac
实例; - 用
SecretKey
初始化Mac
实例; - 对
Mac
实例反复调用update(byte[])
输入数据; - 调用
Mac
实例的doFinal()
获取最终的哈希值。
我们可以用 Hmac 算法取代原有的自定义的加盐算法,然后就可以用来存储密码了,此时表结构类似:
username | secret_key (64 bytes) | password |
---|---|---|
bob | a8c06e05f92e...5e16 | 7e0387872a57c85ef6dddbaa12f376de |
alice | e6a343693985...f4be | c1f929ac2552642b302e739bc0cdbaac |
tim | f27a973dfdc0...6003 | af57651c3a8a73303515804d4af43790 |
# 恢复 SecretKey
除了从 KeyGenerator
生成 SecretKey
之外,我们还可以从一个 byte[]
数组恢复 SecretKey
:
package chapter12Hash;
import java.util.Arrays;
import javax.crypto.*;
import javax.crypto.spec.*;
public class HashDemo6SecretKeySpec {
public static void main(String[] args) throws Exception{
byte[] hkey = new byte[] { 106, 70, -110, 125, 39, -20, 52, 56, 85, 9, -19, -72, 52, -53, 52, -45, -6, 119, -63,
30, 20, -83, -28, 77, 98, 109, -32, -76, 121, -106, 0, -74, -107, -114, -45, 104, -104, -8, 2, 121, 6,
97, -18, -13, -63, -30, -125, -103, -80, -46, 113, -14, 68, 32, -46, 101, -116, -104, -81, -108, 122,
89, -106, -109 };
SecretKey key = new SecretKeySpec(hkey, "HmacMD5");
Mac mac = Mac.getInstance("HmacMD5");
mac.init(key);
mac.update("HelloWorld".getBytes("UTF-8"));
byte[] result = mac.doFinal();
System.out.println(Arrays.toString(result));
//[126, 59, 37, 63, 73, 90, 111, -96, -77, 15, 82, -74, 122, -55, -67, 54]
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
恢复 SecretKey
的语句就是 new SecretKeySpec(hkey, "HmacMD5")
。