消息摘要算法中的HMAC(Keyed-Hashing for Message Authentication)消息认证码算法,MAC(Message Authentication Code,消息认证码算法)是含有密钥散列函数算法,兼容了MD和SHA算法的特性,并在此基础上加上了密钥,因此MAC算法也经常被称作 HMAC 算法。
MAC 是通过【MAC 算法 + 密钥 + 要加密的信息】三个要素一起计算得出的。
HMAC
HMAC 算法首先它是基于信息摘要算法的。目前主要集合了MD和SHA两大系列消息摘要算法。其中MD系列的算法有HmacMD2、HmacMD4、HmacMD5三种算法;SHA系列的算法有HmacSHA1、HmacSHA224、HmacSHA256、HmacSHA384、HmacSHA512五种算法。
HMAC 算法除了需要信息摘要算法外,还需要一个密钥。HMAC的密钥可以是任何长度,如果密钥的长度超过了摘要算法信息分组的长度,则首先使用摘要算法计算密钥的摘要作为新的密钥。一般不建议使用太短的密钥,因为密钥的长度与安全强度是相关的。通常选取密钥长度不小于所选用摘要算法输出的信息摘要的长度。
MD 算法的对比
算法 |
摘要长度(bit) |
实现方 |
HmacMD5 |
128 |
JDK、Bouncy Castle、Commons Codec |
HmacSHA1 |
160 |
JDK、Bouncy Castle、Commons Codec |
HmacSHA224 |
224 |
JDK、Bouncy Castle、Commons Codec |
HmacSHA256 |
256 |
JDK、Bouncy Castle、Commons Codec |
HmacSHA384 |
384 |
JDK、Bouncy Castle、Commons Codec |
HmacSHA512 |
512 |
JDK、Bouncy Castle、Commons Codec |
HmacMD5 使用的key长度是64字节,更安全
HMAC算法分析
HMAC算法本身并不复杂,起需要有一个哈希函数,我们记为H。同时还需要有一个密钥,我们记为K。每种信息摘要函数都对信息进行分组,每个信息块的长度是固定的,我们记为B(如:SHA1为512位,即64字节)。每种信息摘要算法都会输出一个固定长度的信息摘要,我们将信息摘要的长度记为L(如MD5为16字节,SHA-1为20个字节)。正如前面所述,K的长度理论上是任意的,一般为了安全强度考虑,选取不小于L的长度。
HMAC算法其实就是利用密钥和明文进行两轮哈希运算,以公式可以表示如下:
HMAC(K,M)=H(K⊕opad∣H(K⊕ipad∣M))
,其中:
Ipad为0x36重复B次
Opad为0x5c重复B次
M 代表一个消息输入
JDK 的 HMAC 算法实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
public static byte[] getHmacKey(String algorithm) {
try {
// 1、创建密钥生成器
KeyGenerator keyGenerator = KeyGenerator.getInstance(algorithm);
// 2、产生密钥
SecretKey secretKey = keyGenerator.generateKey();
// 3、获取密钥
byte[] key = secretKey.getEncoded();
// 4、返回密钥
return key;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
// HMAC 加密
public static String encryptHmac(byte[] data, byte[] key, String algorithm) {
try {
// 1、还原密钥
SecretKey secretKey = new SecretKeySpec(key, algorithm);
// 2、创建MAC对象
Mac mac = Mac.getInstance(algorithm);
// 3、设置密钥
mac.init(secretKey);
// 4、数据加密
byte[] bytes = mac.doFinal(data);
// 5、生成数据
String rs = encodeHex(bytes);
// 6、返回十六进制加密数据
return rs;
} catch (Exception e) {
throw new RuntimeException(e);
}
}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
public class HmacUtil {
private Mac mac;
public HmacUtil(String key, Algorithm algorithm)
throws NoSuchAlgorithmException, InvalidKeyException {
SecretKey secretKey =
new SecretKeySpec(key.getBytes(StandardCharsets.UTF_8), algorithm.getValue());
mac = Mac.getInstance(secretKey.getAlgorithm());
mac.init(secretKey);
}
public byte[] sign(byte[] content) {
return mac.doFinal(content);
}
public boolean verify(byte[] signature, byte[] content) {
byte[] result = mac.doFinal(content);
return Arrays.equals(signature, result);
}
public enum Algorithm {
HMAC_MD5("HmacMD5"),
HMAC_SHA1("HmacSHA1"),
HMAC_SHA256("HmacSHA256"),
HMAC_SHA384("HmacSHA384"),
HMAC_SHA512("HmacSHA512");
private String value;
Algorithm(String value) {
this.value = value;
}
public String getValue() {
return value;
}
}
}
|
Json 签名
- 保证请求实体只有一层的结构,通过转化为 TreeMap key=value(key,value 保证字符串) 进行 & 拼接参数 参数中包含 [ nonce/key ] [ timestamp ]
- 将 [ secret ] [ nonce/key ] [ timestamp ] 和上述的参数进行拼接
- 最终将这个字符串通过 sha1/或其他算法 生成一个 sign 作为请求参数
- 中括号中的可选字段,最好加上时间戳,通过时间段保证签名安全。
方式二
直接将 request 的 body 进行 HmacSHA256 签名 signature 放在 header。
AppID:应用的唯一标识AppKey:公匙(相当于账号)AppSecret:私匙(相当于密码)
1
2
3
4
5
|
hmac-verify:
header:
nonce: nonce
access-key: AKIAIOSFODNN7EXAMPLE
signature: signature
|
1
|
signature: AWS AWSAccessKeyId:Signature:
|
时间戳放 body
Signature = Base64( HMAC-SHA1( YourSecretAccessKey, UTF-8-Encoding-Of( StringToSign ) ) ) ;
附录