Hmac
算法就是一种基于密钥的消息认证码算法,它的全称是Hash-based Message Authentication Code
,是一种更安全的消息摘要算法。
Hmac
算法总是和某种哈希算法配合起来用的。例如,我们使用MD5算法
,对应的就是HmacMD5算法
,它相当于“加盐”的MD5
:「加盐的定义看后面」
HmacMD5 ≈ md5(secure_random_key, input)
因此,HmacMD5
可以看作带有一个安全的key
的MD5
。使用HmacMD5
而不是用MD5
加salt
,有如下好处:
HmacMD5
使用的key
长度是64
字节,更安全;Hmac
是标准算法,同样适用于SHA-1
等其他哈希算法;Hmac
输出和原有的哈希算法长度一致。
可见,Hmac
本质上就是把key
混入摘要的算法。验证此哈希时,除了原始的输入数据,还要提供key
。
随机加盐
为了保证安全,我们不会自己指定key
,而是通过Java标准库
的KeyGenerator
生成一个安全的随机的key
。下面是使用HmacMD5
的代码:
1 | import java.math.BigInteger; |
和MD5
相比,使用HmacMD5
的步骤是:
- 通过名称
HmacMD5
获取KeyGenerator
实例; - 通过
KeyGenerator
创建一个SecretKey
实例; - 通过名称
HmacMD5
获取Mac
实例; - 用
SecretKey
初始化Mac
实例; - 对
Mac
实例反复调用update(byte[])
输入数据; - 调用
Mac
实例的doFinal()
获取最终的哈希值
固定加盐
有了Hmac
计算的哈希和SecretKey
,我们想要验证怎么办?这时,SecretKey
不能从KeyGenerator
生成,而是从一个byte[]
数组恢复:
1 | import java.util.Arrays; |
恢复SecretKey
的语句就是new SecretKeySpec(hkey, "HmacMD5")
。
上面的代码,等价于
1 | import java.util.Arrays; |
交易所 OKEX 的验证方式
1 | String preHash = preHash(timestamp, method, requestPath, queryString, body); # 一个转换方式 |
加盐
在用户输入原始口令后,系统计算用户输入的原始口令的MD5并与数据库存储的MD5
对比,如果一致,说明口令正确,否则,口令错误。
因此,数据库存储用户名和口令的表内容应该像下面这样:
username | password |
---|---|
bob | f30aa7a662c728b7407c54ae6bfd27d1 |
alice | 25d55ad283aa400af464c76d713c07ad |
tim | bed128365216c019988915ed3add75fb |
这样一来,数据库管理员看不到用户的原始口令。即使数据库泄漏,黑客也无法拿到用户的原始口令。想要拿到用户的原始口令,必须用暴力穷举的方法,一个口令一个口令地试,直到某个口令计算的MD5
恰好等于指定值。
使用哈希口令时,还要注意防止彩虹表攻击。
什么是彩虹表呢?上面讲到了,如果只拿到MD5
,从MD5
反推明文口令,只能使用暴力穷举的方法。
然而黑客并不笨,暴力穷举会消耗大量的算力和时间。但是,如果有一个预先计算好的常用口令和它们的MD5的对照表:
常用口令 | MD5 |
---|---|
hello123 | f30aa7a662c728b7407c54ae6bfd27d1 |
12345678 | 25d55ad283aa400af464c76d713c07ad |
passw0rd | bed128365216c019988915ed3add75fb |
19700101 | 570da6d5277a646f6552b8832012f5dc |
… | … |
20201231 | 6879c0ae9117b50074ce0a0d4c843060 |
这个表就是彩虹表。如果用户使用了常用口令,黑客从MD5
一下就能反查到原始口令:
bob的MD5:f30aa7a662c728b7407c54ae6bfd27d1,原始口令:hello123;
alice的MD5:25d55ad283aa400af464c76d713c07ad,原始口令:12345678;
tim的MD5:bed128365216c019988915ed3add75fb,原始口令:passw0rd。
这就是为什么不要使用常用密码,以及不要使用生日作为密码的原因。
即使用户使用了常用口令,我们也可以采取措施来抵御彩虹表攻击,方法是对每个口令额外添加随机数,这个方法称之为加盐(salt):
digest = md5(salt+inputPassword)
经过加盐处理的数据库表,内容如下:
username | salt | password |
---|---|---|
bob | H1r0a | a5022319ff4c56955e22a74abcc2c210 |
alice | 7$p2w | e5de688c99e961ed6e560b972dab8b6a |
tim | z5Sk9 | 1eee304b92dc0d105904e7ab58fd2f64 |
加盐的目的在于使黑客的彩虹表失效,即使用户使用常用口令,也无法从MD5
反推原始口令。