使用HMAC-SHA1进行API身份验证-如何安全存储客户端密码?

34
在使用S3-style身份验证的RESTful API中,API客户端使用HMAC-SHA1使用其密钥对请求进行签名,因此密钥永远不会通过网络传输。 然后,服务器使用客户端的密钥重复签名过程并将结果与客户端传输的签名进行比较来对客户端进行身份验证。
这很好,但这意味着服务器需要访问客户端共享密钥的明文。这与所有建议不要在数据库中以明文形式存储用户密码的建议相矛盾。据我所知,仅存储密码的盐哈希值不是选项 - 因为然后我无法验证客户端的签名。
我应该强调我的API是RESTful的,因此应该是无状态的:我宁愿避免在其他API调用之前进行登录步骤。
一个可选的解决方案是使用某些对称密钥算法加密所有用户密码。但是,服务器必须将该加密的密钥存储在某个易于访问的地方,例如源代码中。这比没有更好,但不是最佳解决方案(正如@Rook在他的答案中提到的那样,它违反了CWE-257)。
另一个解决方向可能是围绕非对称签名,但我无法想象如何将其应用于HMAC,并且找不到任何有关该主题的文章。
我错过了什么明显的东西吗?许多受人尊敬的提供商都实施了这种身份验证方案 - 他们不能全部违反常见的安全原则,对吧? 如果没有,您可以分享任何最佳实践吗?
3个回答

39

这是对称密钥挑战-响应式身份验证的缺点 - 您不会将密码传输到网络上,但必须在两端存储密码(HMAC是对称密钥系统)。

需要注意的是,这不是密码-而是共享密钥。这里有一个根本性的差别-密码通常由用户选择,而共享密钥是随机生成并提供给用户的(在此情况下通常称为“API密钥”)。

以可逆格式存储密码很糟糕,因为如果您的数据库受到攻击,则攻击者可能已经获得了在其他地方使用过的密码。另一方面,存储共享密钥则不是很大的问题-该密钥仅特定于您的服务,因此攻击者获得的只是登录到您的服务的能力。

另一方面,也可以拥有一个不必在服务器端存储密钥的非对称系统。基本思想是服务器知道客户端的公钥和当前消息序列号。发送API请求时,客户端增加消息序列号,并计算出一个签名,签名包括序列号和API请求参数,服务器可以使用公钥进行验证。服务器会拒绝包含旧消息序列号的消息,以防止重放攻击。


感谢@caf。澄清密码和共享密钥之间的区别是一个好观点。即使我的服务出了问题,至少我没有让其他人出问题 :) - Elad
6
您所描述的非对称系统是一个不错的方案!但这里存在一个权衡(通常都会有吧?)。问题在于现在客户端和服务器必须进行协调。这是一个充满奇怪 bug 的潘多拉魔盒,除非您意识到它们已经失序,否则没有人能够理解这些 bug。它还会产生并发问题(例如,如果客户端应用程序有多个实例访问 API)。简而言之,为了安全性增加了很多复杂性。 - Elad
+1 是为了澄清“共享密钥”和“密码”的区别。这个答案恰好符合我的要求。 - Rafael Ibraim
1
仍然不明白您所说的序列号是什么意思。它难道不应该像一次性密码那样起作用吗?它如何消除存储共享密钥的需要呢? - frostymarvelous
@frostymarvelous 签名可以使用私钥进行创建,并使用公钥进行验证。如果服务器知道客户端的公钥,则可以验证签名以对客户端进行身份验证。如果签名正确,则可以确保请求来自客户端而不是其他人,除非客户端的私钥已被泄露。需要该数字以使每个请求到服务器的签名每次都不同。否则,某人可能会抓取数据包并再次发送它 >( - Jeroen Landheer
@Jeroen Landheer,这看起来是一个可行的解决方案。对于原生应用程序也存在相同的安全威胁,所以我认为这是一个可以接受的风险。 - frostymarvelous

11

在用户登录后,理想情况下,您会为其提供一个加密的随机数,该随机数在该会话的生命周期内用作HMAC密钥K。这是一种安全的方法,但它不符合REST的要求,因为REST是无状态的。每次登录时发放一个消息认证代码的想法从技术上来说是一种状态。

加密密码并将其存储在数据库中是违反CWE-257的规定。


谢谢@Rook。我应该提到我的API是RESTful的,因此我宁愿避免登录步骤。我已经相应地编辑了我的问题。 - Elad
5
可以提出这样的论点:您不能真正做到“无状态”并进行身份验证,因为用户名和密码是一种状态。当然,每个经过身份验证的REST实现都会为了安全而做出这种妥协。 - rook
我相信你是对的,但是我希望我的问题能被证明是错误的 :) 我会继续保持开放的状态,以防有人提出完美的解决方案。 - Elad
@Elad 这是一个非常值得讨论和解释的好问题。 - rook
@Elad 这篇文章让我想起了一些问题,所以我不得不去问SO:https://dev59.com/questions/YFXTa4cB1Zd3GeqP4b2c - rook
抱歉这个问题问得有点晚了。但是,服务器发送的加密随机数如何到达客户端呢?如果中间有人拿到了随机数会发生什么(假设没有SSL/TLS)?我正好遇到了这个问题,但似乎无法解决。 - html_programmer

2

我不确定是否有遗漏,但其中一种选择是使用哈希密码作为对称密钥。


2
那么消费者也需要知道你如何哈希密码,以及是否使用盐......现在你应该看到问题了。 - frostymarvelous
否则,用户将需要知道用于登录的哈希值。这不是一个简单的不便。 - frostymarvelous

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接