HMAC(Hash-Based Message Authentication Code)由H.Krawezyk,M.Bellare和R.Canetti于1996年提出的一种基于Hash函数和密钥进行消息认证的方法,并于1997年作为RFC2104被公布,并在IPSec和其他网络协议(如SSL)中得以广泛应用,现在已经成为事实上的Internet安全标准。它可以与任何迭代散列函数捆绑使用。
HMAC算法除了需要消息摘要算法外,还需要一个密钥。HMAC的密钥可以是任何长度,如果密钥的长度超过了摘要算法信息分组的长度,则首先使用摘要算法计算密钥的摘要作为新的密钥。一般不建议使用太短的密钥,因为密钥的长度与安全强度是相关的。通常选取密钥长度不小于所选用摘要算法输出的信息摘要的长度。
HMAC认证,主要是为了能让人对对方身份正确性和消息有效性进行验证,与消息摘要的最大不同,就是有签名密钥。而摘要算法,只是能够证明消息的有效性。HMAC被广泛地运用于网络协议的认证阶段,例如邮件协议使用到了它,SSL也有它的身影。
二、举例理解HMAC的核心思想是身份认证和消息有效性认证。
1、前提条件在消息认证码生成的一方和校验的一方, 必须有一个共同的秘钥。双方约定好使用同样的哈希函数对数据进行运算。2、流程发送者:发送原始法消息将原始消息生成消息认证码:哈希函数(原始消息 + 秘钥) = 散列值(消息认证码)将消息认证码发送给对方接收者:接收原始数据接收消息认证码校验:哈希函数( 接收的消息 + 秘钥 ) * 哈希函数 = 散列值,将本地计算的散列值和接收的散列值进行比较。三、HMAC算法表示
算法公式 :
HMAC(K,Text)= H( (K⊕opad) | H( (K⊕ipad) | Text))
H 代表所采用的HASH算法(如MD5、SHA1、SHA-256等)。
K 代表认证密码。
Text 代表一个消息输入。
⊕代表异或。
|表示两个消息合并。
Opad 用0x5c重复填充大小为B的数组。
Ipad 用0x36重复填充大小为B的数组。
Apad 用0x878FE1F3重复(L/4)次。
B 代表H中所处理的块大小,这个大小是处理块大小,而不是输出hash的大小。
如MD5、SHA-1和SHA-256对应B = 64, SHA-384和SHA-512 对应B = 128。
L 表示hash结果的大小,L=16 for MD5, L=20 for SHA-1。
C演示代码表示:
void hmac_md5(text, text_len, key, key_len, digest)unsigned char *text; /* pointer to data stream */int text_len; /* length of data stream */unsigned char *key; /* pointer to authentication key */int key_len; /* length of authentication key */unsigned char *digest; /* output: digest */{ unsigned char tk[16]; unsigned char k_ipad[64]; /* inner padding - * key XOR with ipad */ unsigned char k_opad[64]; /* outer padding - * key XOR with opad */ MD5_CTX context; /* if key is longer than 64 bytes reset it to key=MD5(key) */ if (key_len > 64) { MD5_CTX tctx; MD5Init(&tctx); MD5Update(&tctx, key, key_len); MD5Final(tk, &tctx); key = tk; key_len = 16; } /* start out by storing key in pads */ memset(k_ipad, 0, sizeof(k_ipad)); memset(k_opad, 0, sizeof(k_opad)); memcpy(k_ipad, key, key_len); memcpy(k_opad, key, key_len); /* XOR key with ipad and opad values */ for (int i=0; i<64; i++) { k_ipad[i] ^= 0x36; k_opad[i] ^= 0x5c; } /* perform inner MD5 */ MD5Init(&context); /* init context for 1st pass */ MD5Update(&context, k_ipad, 64) /* start with inner pad */ MD5Update(&context, text, text_len); /* then text of datagram */ MD5Final(digest, &context); /* finish up 1st pass */ /* perform outer MD5 */ MD5Init(&context); /* init context for 2nd pass */ MD5Update(&context, k_opad, 64); /* start with outer pad */ MD5Update(&context, digest, 16); /* then results of 1st hash */ MD5Final(digest, &context); /* finish up 2nd pass */}
四、运算步骤如果密钥K的长度大于B的字长,对密钥K计算hash(K), 结果当作输入密钥;在密钥K后面添加0来创建一个字长为B的字符串(例如,如果K的字长是20字节,B=64字节,则K后会加入44个零字节0x00);将第(2)步生成的B字长的字符串与ipad做异或运算;将数据流text补充至第(3)步的结果字符串的尾部;用H作用于第(4)步生成的数据流;将第(2)步生成的B字长字符串与opad做异或运算;再将第(6)步的结果补充至第五步的结果的尾部;用H作用于第(7)步生成的数据流,输出最终结果。
由上述描述过程,我们知道HMAC算法的计算过程实际是对原文做了两次类似于加盐处理的哈希过程。
HMAC的密钥长度可以是任意大小,如果小于n(hash输出值的长度),那么将会削弱算法安全的强度。建议使用长度大于n的密钥,但是采用长度大的密钥并不意味着增强了函数的安全性。密钥应该是随机选取的,可以采用一种强伪随机发生器,并且密钥需要周期性更新,这样可以减少弱密钥的危险性以及已经暴露密钥所带来的破坏。
五、计算结果演示消息:ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789
密钥:i love you
# | HMAC函数 | 计算结果 | 字节长度 |
1 | Hmac-md2 | 62b22a645d7f331422b4d087e0558675 | 16 |
2 | Hmac-md4 | fbff23b3c9903291fdc5a00d410b77a8 | 16 |
3 | Hmac-md5 | 79e44af262dc67cf7feefc2a08a2fda6 | 16 |
4 | Hmac-sha1 | ea1a4b8e9185f607ba02b9773cb7dadd736de12b | 20 |
5 | Hmac-sha256 | 8635a9cfa8c0f8569cb08b4268446df0c5a9088a407b25a09dd5f050bd0d5cf7 | 32 |
6 | Hmac-sha512 | 4e9efe08ad0229636975e9f756c6802cce93e75adbfaacf94cad9bdf9dba35a9e3bdd42619bce98b6d4c118b8a7431bb9b15ea88ba26d8de365252c7308be39b | 64 |
7 | Hmac-sha224 | 5235a3edbc52ed72fb1cc3bb9d32745b56eaee4e91a056019ee75116 | 28 |
8 | Hmac-sha384 | 15ae925c4dc725c155a5155c7877e0fd29b7dc6f2e811f2b0252112a7aa2fee3cf2ee40f8642805850862e0ffb601053 | 48 |
9 | Hmac-sha3-224 | 9d53a512f6cc446734627cdea59057c254db3bbc7f490a35e680fd08 | 28 |
10 | Hmac-sha3-256 | df7cd9e4c124a764ae151694b2acae7cec0490a0ddeece8e9e76023afdbf5dbc | 32 |
11 | Hmac-sha3-384 | 4bb7e1eda41dd5b803b1bfbdf93070d757b18c6e18e759155bfd2fed5000ef4d4630b2d55ad2f0a6d18631b2c338c997 | 48 |
12 | Hmac-sha3-512 | bf8c6284396c6c3fab7bab7558109715ed40c9b8012391fd51c23f5c57143aa1aba7dd5e0a2106fbda3e0c4d547015aa2911ce9049201409bd618cd71b87b3e8 | 64 |
目前用来对消息进行认证的主要方式有2种: 消息加密和消息认证码。
消息加密:它利用分组密码对整个消息加密,将密文作为认证符;消息认证码:秘钥+散列函数。消息认证码的方式比消息加密的方式计算速度更快,这对于高速网络业务处理来说,分秒必争。
消息认证码的关键是散列函数的选择,这就要在安全强度和速度两方面综合考虑。
HMAC算法首先它是基于消息摘要算法的,目前主要集合了MD和SHA两大系列消息摘要算法,其中MD系列的算法有HMAC-MD2、HMAC-MD4、HMAC-MD5三种算法;SHA系列的算法有HMAC-SHA1、HMAC-SHA224、HMAC-SHA256、HMAC-SHA384、HMAC-SHA512五种算法。
HMAC算法还支持大量的消息摘要算法: md5、sha1、sha256、sha512、adler32、crc32、crc32b、fnv132、fnv164、fnv1a32、fnv1a64、gost、gost-crypto、haval128,3、haval128,4、haval128,5、haval160,3、haval160,4、haval160,5、haval192,3、haval192,4、haval192,5、haval224,3、haval224,4、haval224,5、haval256,3、haval256,4、haval256,5、joaat、md2、md4、ripemd128、ripemd160、ripemd256、ripemd320、sha224、sha3-224、sha3-256、sha3-384、sha3-512、sha384、sha512/224、sha512/256、snefru、snefru256、tiger128,3、tiger128,4、tiger160,3、tiger160,4、tiger192,3、tiger192,4、whirlpool。
七、HMAC独特优势与数字签名比较,HMAC并不完美,存在一个弊端和两个无法解决的问题。
弊端:秘钥分发困难。
无法解决的问题:不能进行第三方证明和不能防止否认。
理想往往败给现实,数字签名需要分发公钥与私钥,且签名验证速度慢,证书颁发还要到专门的机构申请和缴费。HMAC对密码的使用很处理比较简单,对于要求快速处理的业务来说,其好处的吸引力非常诱人。
而与分组密码认证相比,HMAC有几大优势:
一般的散列函数的软件执行速度比分组密码更快;密码散列函数的库代码来源广泛;如果找到或需要更快或更安全的散列函数,能够很容易地代替原来嵌入的散列函数。八、典型应用1、IPSec和SSL协议中的应用HMAC算法广泛应用在在互联网的安全认证和安全传输中。
在浏览网页时,https开头的网址都是用到了SSL协议,用于保证网页内容的安全,这是由HMAC算法实现的。
IPSec VPN是实现远程接入的一种VPN技术,IPSec全称为Internet Protocol Security,在公网上为两个私有网络提供安全通信通道, 通过加密通道保证连接的安全—在两个公共网关间提供私密数据封包服务。在使用公网传送内部专网的内容时,IPSec VPN在IP传输上通过加密隧道,保证在IP公网上传输的数据的安全性,从而实现总部到分支机构之间的语音,视频等数据的互通。如今,很多国际的企业已经把VPN作为总部到分支机构的主要连接手段,比如的国际大型的组织,商业机构,连锁门店已经采用VPN这种安全的技术。这种VPN技术中就使用了HMAC算法。
2、API接口签名验证我们在网上购物、买票、银行转账、炒股等都用到了HMAC算法。各大银行和BAT大厂的后台与前端都用到了API接口签名验证。
用户需要先在网站上申请key、secret,然后校验流程如下:
方法一 参数方式请求接口:http://api.test.com/test?name=hello&home=world&work=java
客户端:
1> 生成当前时间戳timestamp=now和唯一随机字符串nonce=random;
2> 按照请求参数名的字母升序排列非空请求参数(包含accessKey)
stringA="home=world&name=hello&work=java&accessKey=key×tamp=now&nonce=random";
3> 计算签名signature = HMAC-SHA256(secret, stringA);
4> 最终请求
http://api.test.com/test?name=hello&home=world&work=java&accessKey=key×tamp=now&nonce=nonce&sign=signature.
服务器:
1> 按照请求参数名的字母升序排列非空请求参数(包含accessKey)
stringB="home=world&name=hello&work=java&accessKey=key×tamp=now&nonce=random";
2> 通过key在数据库获取secret;
3> 计算签名signature=HMAC-SHA256(secret, stringB);
4> 将signature与接收的签名串比较。
方法二 请求体Body方式客户端:
1> 将请求参数封装成json字符串,也就是请求体body;
2> 使用HMAC-SHA256算法加secret对Msg(请求url+timestamp+nonce+body)加密生成摘要signature;
3> 将key,signature放入header中一并传给服务器。
服务器:
1> 获取请求中的请求体body字符串;
2> 使用HMAC-SHA256算法加secret(通过header中的key在数据库获取)对Msg(请求url+timestamp+nonce+body)加密生成摘要signature;
3> 服务端生成的摘要串与客户端通过header传递过来的摘要串进行比较。
timestamp+nonce方案(防止重放攻击)nonce指唯一的随机字符串,用来标识每个被签名的请求。通过为每个请求提供一个唯一的标识符,服务器能够防止请求被多次使用(记录所有用过的nonce以阻止它们被二次使用)。
假设允许客户端和服务端最多能存在15分钟的时间差,同时追踪记录在服务端的nonce集合。当有新的请求进入时,首先检查携带的timestamp是否在15分钟内,如超出时间范围,则拒绝,然后查询携带的nonce,如存在已有集合,则拒绝。否则,记录该nonce,并删除集合内时间戳大于15分钟的nonce(可以使用redis的expire,新增nonce的同时设置它的超时失效时间为15分钟)。
注意使用HMAC-SHA256更加安全,而且我们可以直接将请求参数封装成json字符串放入请求体中(也就是通过io流)进行传递。
方法三 Token+AppKeyToken身份验证1> 用户登录向服务器提供认证信息(如账号和密码),服务器验证成功后返回Token给客户端;
2> 客户端将Token保存在本地,后续发起请求时,携带此Token;
3> 服务器检查Token的有效性,有效则放行,无效(Token错误或过期)则拒绝。
为客户端分配AppKey(密钥,用于接口加密,不参与传输)将所有请求参数组合成串,根据HMAC+AppKey签名算法生成签名值,发送请求时将签名值一起发送给服务器验证。这样,即使Token被劫持,对方不知道AppKey,就无法伪造请求和篡改参数。再结合重发攻击解决方案,即使请求参数被劫持也无法伪造二次重复请求。3、客户端与服务器身份认证(1) 先由客户端向服务器发出一个验证请求;
(2) 服务器接到此请求后生成一个随机数并通过网络传输给客户端(此为挑战);
(3) 客户端将收到的随机数提供给ePass,由ePass使用该随机数与存储在ePass中的密钥进行HMAC-MD5运算并得到一个结果作为认证证据传给服务器(此为响应);
(4) 与此同时,服务器也使用该随机数与存储在服务器数据库中的该客户密钥进行HMAC-MD5运算,如果服务器的运算结果与客户端电脑传回的响应结果相同,则认为客户端是一个合法用户。
电脑