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反推原始口令。