我不确定你是否混淆了加密和哈希。如果用户的密码被
加密 而不是
哈希,那么在数据泄露事件中攻击者有可能窃取所有用户密码。
当涉及身份验证时,有一些因素似乎被忽略了。首先,任何哈希都应该在后端完成,而不是在前端完成。在前端进行哈希仍然会使您容易受到哈希攻击。
一些开发人员采用双重哈希方法,即在前端哈希密码,然后在后端重新哈希密码。我认为这是不必要的,前端密码应该由 HTTPS 层 (
TLS) 进行覆盖,但这是有待讨论的。
首先,让我们澄清两个关键术语,然后解释如何安全地存储和验证用户。
加密
你指定用户的密码被
加密 而不是哈希。加密函数将输入(用户密码)映射到输出(加密密码),这意味着这是
可逆的。
这意味着如果黑客获取了加密密钥(私钥),他们可以轻松地反转整个过程。
散列值
相反,用户的密码应该在服务器端进行哈希。为什么?因为你可以通过比较两个哈希值来检查它们是否匹配而不必存储该值的明文表示形式。
再次提醒,你可能会问,“为什么”? 嗯,因为哈希函数是单向的,这意味着无法将明文值反转(好吧,它们很难),我不会详细介绍。
我该怎么做?
用户的密码永远不应以明文形式存储在Web服务器的任何部分中。相反,应该存储用户的哈希值。当用户尝试登录时,你通过HTTPS / TLS安全地接收其明文密码,对其进行哈希,如果两个哈希匹配,则认证用户。
因此,数据库表可能如下所示:
+
| ID | Username | Password Hash |
+
| 1 | foo | $2a$04$/JicM |
| 2 | bar | $2a$04$cxZWT |
+
- 注意,哈希值是使用4轮(也称为无效)截断的BCrypt哈希值
现在让我们以Alice和我们的服务器之间的示例为例。不要过于字面理解数据。
Alice使用她的凭据发送登录请求,首先通过我们的安全传输层:
{username: "foo", password: "bar"} -> TLS() -> ZwUlLviJjtCgc1B4DlFnK -> | Server |
我们的服务器接收到这个请求后,使用它的证书密钥来解密:
ZwUlLviJjtCgc1B4DlFnK -> KEY() -> {username: "foo", password: "bar"} -> Web Application
太好了!我们的凭据已经安全传递了,那么接下来呢?对密码进行哈希处理并与我们在数据库中得到的进行比较。
BCRYPT('bar') -> $2a$04$/JicM
if ($2a$04$/JicM == user.get_password_hash) {
authenticate();
}
else {
return status_code(401);
}
我们现在已经能够验证用户身份,存储一个不可逆的哈希值,而无需存储明文值。这应该已经回答了您的第一个和第二个问题。