目录

消息摘要算法 HMAC

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

附录