消息摘要算法中的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 ) ) ) ;
附录