MAC 和哈希函数、签名的冤孽(看JWT有感)

我在看 Json web token (JWT) 的时候,发现网上很多文章对 HMAC 有很多误解,比如在讨论 HMACSHA256/HMACSHA512 时,加密、私钥、签名各种误解混杂在其中。因此写了这篇文章,抛砖引玉。

HMAC(Hash-based Message Authentication Code) 是一种基于哈希的消息验证码,由Mihir Bellare、Ran Canetti和Hugo Krawezyk 于1996年提出。这里有2个关键词,一是哈希(即哈希函数),二是验证码。 事实上HMAC 是 MAC(Message Authentication Code) 的一种实现。

因此只要了解了 MAC,我们也就大概明白了 HMAC。 MAC 可以通过对称密钥和哈希函数一起构建消息验证码,也可以通过对称密钥和对称加密算法(比如AES)一起构建消息验证码。比如 HMAC 使用的是就是哈希函数,而 CBC-MAC 使用的就是对称加密算法。 我们看下 HMAC 的公式,如下:

\[\text{HMAC}_k(x) = h[(k^+ \oplus \text{opad}) || h[(k^+  \oplus \text{ipad}) || x]]\]

其中x 是消息, h 是哈希函数,$k^+$ 是对称密钥k的填充后的值, opad 是外部填充, ipad 是内部填充, $\oplus$ 是异或操作, $||$ 是连接操作。


关于哈希函数可以参考我的另外一篇文章 聊聊生日悖论和生日攻击


简单的总结下:

1. MAC 是哈希函数吗?不是,哈希函数是没有密钥的,但是MAC 存在一个对称密钥。HMAC 的实现用到了哈希函数而已。

2. HMAC 能说是加密操作吗?不能,因为HMAC 使用的是哈希函数,不能解密还原明文,CBC-MAC 才是加密操作。当 JWT 使用HMAC 算法时,它的 token 是不能存放秘密信息或敏感信息的,因此 token 中 payload 私有声明的部分是也是明文的,这里存放的实际上是私有定制化的东西。

3. MAC 和RSA 非对称算法签名不同,MAC生成验证码和验证验证码使用同一个密钥,这个密钥不能泄露出去。JWT 的密钥只能存放在服务端,前端或客户端不能存放。

4. MAC 算数字签名吗?一般密码学领域把RSA非对称签名称之为数字签名,由于MAC 是对称的,不能称之为数字签名。MAC 可以保证消息的完整性,以及验证消息,但是MAC 不具有不可否认性(因为通讯的双方共享同一个密钥,双方都有作案的可能),这是不能称之为数字签名的根本原因,一般称为消息验证。

5. 从4来看MAC似乎比RSA数字签名弱,那为什么还要发明MAC ? MAC 一个优势是比数字签名运算更快;另外有些场景我们并不需要"不可否认性"那么强的属性,只要能验证消息的完整性(被窜改了可以检测到),以及验证消息的来源(比如确实是自己生成的,不是别人)。 JWT 就是这种场景。


另外我看到一个Star 有20多k的开源项目在实现 JWT 时,存在一个不安全的实现,使用 HMAC 算法时,密钥使用的是用户的密码作为密钥。 这里我猜想作者原意是不同的用户使用不同的密钥,以便增强安全性,可是这样却好心变坏事了。这样做有2个问题:


1. 安全性:没有增强安全,反倒降低了安全。原因是作为开源项目大家都知道你用户密码使用的哈希函数和加盐信息(即使不是开源项目,内部项目的项目成员也是知晓的,离职后想干坏事就容易了),那么只要有个用户使用了弱密码或中等强度的密码,并且这个用户的某个 token 被泄露出去了(不论是否过期都可以),那么黑客就可以写个代码在短时间内暴力破解出密钥。


2. 性能变差:即使认证成功后,每次请求都需要去数据库查询获取对应用户的密码,用户量一大请求多的时候用户表查询变成了热点,访问必然变卡。不过该项目属于后台管理,所以作者对用户信息加了缓存,因此这个问题没有暴露出来。


欢迎关注我的微信公众号[数学345]:长按"识别图中二维码";或打开微信扫一扫。

评论(0)