使用 HMAC-SHA1 进行 API 身份验证 - 如何安全地存储客户端密码?
在使用 S3 样式 身份验证的 RESTful API 中,API 客户端使用 HMAC-SHA1 使用其密钥对请求进行签名,因此密钥永远不会通过线路传输。 然后,服务器通过使用客户端的密钥重复签名过程本身并将结果与客户端传输的签名进行比较来对客户端进行身份验证。
这一切都很好,但这意味着服务器需要访问客户端共享密钥的明文。这与所有反对在数据库中以明文形式存储用户密码的建议背道而驰。据我所知,仅存储密码的加盐哈希值不是一个选项 - 因为那样我就无法验证客户端的签名。
我应该强调,我的 API 是 RESTful,因此应该是无状态的:我宁愿避免在其他 API 调用之前执行登录步骤。
一种可选的解决方案是使用某种对称密钥算法加密所有用户密码。然而,服务器必须将加密密钥存储在易于访问的地方,例如源代码内。这比没有好,但不是最佳解决方案(正如 @Rook 在他的回答中提到的,它违反了 CWE-257)。
解决方案的另一个方向可能是围绕非对称签名的问题,但我不知道如何将其应用到 HMAC,也找不到有关该主题的任何文章。
我在这里遗漏了一些明显的东西吗?许多受人尊敬的提供商已经实施了这种身份验证方案 - 他们不可能都违反共同的安全原则,不是吗? 如果没有,您可以分享一些最佳实践吗?
In a RESTful API that uses S3-style authentication, the API client signs the request with his secret key using HMAC-SHA1, so the secret key is never transmitted over the wire.
The server then authenticates the client by using that client's secret key to repeat the signature process itself and compare the result to the signature transmitted by the client.
This is all nice and good but it means the server requires access to the plaintext of the client's shared secret. That flies in the face of all the advice out there against storing user passwords in the clear inside your database. Storing only the salted hash of the password is not an option as far as I can tell - because then I can't verify the client's signature.
I should stress that my API is RESTful and thus should be stateless: I'd rather avoid a login step prior to other API calls.
One optional solution is to encrypt all user passwords using some symmetric key algorithm. However, the server would have to store the key to that encryption somewhere easily accessible, e.g. inside the source code. This is better than nothing but not an optimal solution (as @Rook mentioned in his answer, it violates CWE-257).
Another direction for a solution could be something around asymmetric signatures, but I can't figure out how to apply that to the HMAC, and can't find any articles on the subject.
Am I missing something obvious here? Many respectable providers have implemented this kind of authentication scheme - they can't all be violating common security principles, can they?
If not, are there any best practices that you can share?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
这是对称密钥质询-响应式身份验证的缺点 - 您不会将秘密放在网络上,但必须在两端存储秘密。 (HMAC 是对称密钥系统)。
请注意,这不是一个密码 - 它是一个共享秘密。这里有一个根本的区别 - 密码通常由用户选择,而共享秘密是随机生成并提供给用户的(在这种情况下,它们通常称为“API 密钥”)。
以可逆格式存储密码是不好的,因为如果您的数据库遭到破坏,那么攻击者就获得了可能(并且可能已经)在其他地方使用的密码。另一方面,存储共享秘密并不是一个问题 - 该秘密是特定于您的服务的,因此攻击者获得的只是登录到您的服务的能力。
另一方面,有可能拥有一个不必在服务器端存储秘密的非对称系统。基本思想是服务器知道客户端的公钥和当前消息序列号。发送 API 请求时,客户端递增消息序列号,并根据序列号和 API 请求参数计算签名,服务器可以使用公钥验证该签名。如果消息包含旧消息序列号,服务器将拒绝该消息,以防止重放攻击。
This is the downside of symmetric-key challenge-response style authentication - you don't put the secret on the wire, but you have to store the secret at both ends. (HMACs are symmetric key systems).
Note though that it's not a password - it's a shared secret. There's a fundamental difference here - a password is generally chosen by the user, whereas a shared secret is generated randomly and provided to the user (they're often called "API keys", in this context).
Storing passwords in a reversible format is bad, because if your database is compromised, then the attackers have obtained passwords that might (and probably have been) used elsewhere. Storing a shared secret, on the other hand, is not such a problem - the secret it's specific to your service, so all the attackers have gained is the ability to log in to your service.
On the other hand, it is possible to have an asymmetric system that doesn't have to store a secret at the server side. The basic idea is that the server knows the client's public key and current message sequence number. When sending an API request, the client increments the message sequence number and calculates a signature over the sequence number and the API request parameters, which the server can verify using the public key. The server rejects a message if it contains an old message sequence number, to prevent replay attacks.
理想情况下,在用户登录后,您向他们提供一个加密随机数,该随机数用作该会话生命周期内的 HMAC 密钥 K。这是一种安全的方法,但它不是 RESTful,因为 REST 是无状态的。每次登录时发出消息验证码的想法在技术上是一种状态形式。
加密密码并将其存储在数据库中违反了 CWE-257。
Ideally after the user logs in you give them a Cryptographic Nonce which is used as the HMAC secret key K for the life of that session. This is a secure approach, but its not RESTful, because REST is stateless. This idea of an message authentication code issued per login is technically a form of state.
Encrypting passwords and storing them in the database is a violation of CWE-257.
我不确定我是否在这里遗漏了一些东西,但一种选择是使用散列密码作为对称密钥。
I am not sure if i am missing something here but one option is to use hashed password as symmetric key.