在客户端散列密码以避免在单核游戏服务器上滞后?

信息安全 密码 哈希 javascript bcrypt
2021-09-04 07:38:40

我目前正在实现一个简单的登录系统。我想将所有用户密码安全地存储在我的数据库中。密码是最重要的组成部分。所有其他用户数据都不重要。如果账号被黑了,只要查不出密码就可以了;其他数据包括:分数和帐户创建日期。

我假设我的一些用户将使用与其他网站相同的密码。所以我需要确保潜在的黑客无法读取密码。

我可以在客户端以某种方式生成哈希以避免任何黑客看到真正的密码吗?

在你说我不应该在客户端上做之前。服务器有一个需要运行的恒定游戏循环,我不希望每次有人注册帐户或尝试登录时游戏都会滞后。这种方法将减轻服务器的一些负载。

编辑:由于这个问题已经结束,我想详细说明一下。移动到客户端允许我在对密码进行哈希处理时使用更多轮次,因为这不会以任何方式影响服务器或其他用户的体验。这也使得防止用户发送垃圾邮件请求和降低服务器速度变得更加容易。

3个回答

我假设我的一些用户将使用与其他网站相同的密码。

在保护密码不被其他网站(除了您自己的)使用的情况下,您是否散列密码client-side 或 server-side都没有关系

但是,根据您的客户端环境(即 Web 浏览器),您可能很难找到足够优化(强)的慢速密码哈希实现。(例如 Argon2 或 BCrypt)因此,您对客户端散列的建议可能仅在 本机加密可用的应用程序环境中可行。

当然,散列客户端不会保护密码在您的站点上不被使用。窃取哈希的攻击者可以简单地跳过客户端哈希例程并通过您的服务器进行身份验证。(永远不要相信客户端)幸运的是,这种攻击向量可以在服务器端解决(见下文)

服务器有一个需要运行的恒定游戏循环,我不希望每次有人注册帐户或尝试登录时游戏都会滞后。这种方法将减轻服务器的一些负载。

如您所知,传统上建议对服务器端进行哈希处理。如果您担心负载,我提出一些建议:

  1. 工作因数应该调整为仅在目标硬件上花费约 100 毫秒(您的服务器)所以,这并不是一个真正的滞后,是吗?我通常提倡 50ms-500ms,但您的网站可以使用较低的值,但仍然被认为是健壮的。

  2. 哈希应该与主游戏循环(异步)处于单独的线程中,以便游戏可以在哈希计算时继续运行(尽管性能稍差)。

  3. 您应该包括某种形式的DoS 保护来限制您的服务器将计算的哈希量。通常,该站点会限制登录尝试(每个用户?),以便攻击者无法有效地暴力破解。这样的节流阀将阻止无休止的哈希计算。

  4. 如果您愿意,如果客户端环境有足够的原生支持,您可以ClientServer密码进行哈希处理。(如上所述)然后您可以使用更小的工​​作因素服务器端。

    如果客户端有足够强的工作因子,那么在服务器端只运行一个快速(即零延迟)的 SHA-2 实际上是合理的,这将完全解决您的问题。感谢@HenningMakholm

最后,确保客户端仅通过 HTTPS发送密码(或哈希) 。这是最重要的事情,因为没有多少假设的散列可以保护输入到 HTTP 站点的密码。

如果目标只是在您的数据库遭到破坏时保护用户免受密码重用,那么是的,您可以将散列移动到客户端。

您需要接受这通常不足以真正保护您的应用程序。这是不够的,因为一般来说,大多数此类方法都有以下两个错误之一:

  1. 他们传输一个“类似密码”的令牌,从某种意义上说,如果我确实打破了你对 HTTPS 或其他任何东西的假设,我就会以你的身份登录。

  2. 他们存储了一个“类似密码”的令牌,如果我确实查看了用户表中的一行,我就可以以该用户身份登录。

第一个是任何客户端操作,以“然后我传输到服务器,服务器对其进行哈希处理,服务器比较哈希是否等于数据库中的某个值”结尾。第二个是挑战-响应方案的特点,“我把这个字符串发给你,你用你的密钥加密它,你把加密的版本发回,我看看我能不能解密它来再次得到原来的挑战。”

我非常仔细地为第二种情况选择了用词,因为它表明,如果你的系统基于非对称加密、公钥和私钥,你可以避免这两种情况。如果您的客户端可以计算私钥,则您的服务器可以存储公钥。这过去对于 RSA 等是完全不可行的。(“哦,我将只使用密码作为确定性随机数生成器的种子并每次都进行密钥设置”——你在开玩笑吗?)但现在变得更有可能了椭圆曲线密码学,其中私钥通常可以是一定长度的任意位串。您使用密钥派生函数来获得它,并且您是安全的。

但是,在任何情况下,您都可能不想“滚动自己的”加密:在这种情况下,请寻找在斯坦福创建的安全远程密码 (SRP) 协议的实现;它已经看到了很多密码分析,并且已经存在了一段时间,并且可能具有您可以找到和使用的实现。

您可以在客户端执行昂贵的密码散列部分,但您需要小心。例如,以下协议将是错误的

  1. 客户端建立到服务器的经过身份验证的机密通道。(例如,具有有效服务器证书的 SSL 连接。)
  2. 客户端发送username到服务器
  3. 服务器查找对应的密码条目 (username, salt, hashed_pwd)
  4. 服务器发送salt给客户端。
  5. 客户端使用代价高昂的密码散列对其密码进行散列: client_hash = pwhash(salt, pwd)
  6. 客户端发送client_hash到服务器。
  7. 服务器client_hash比较hashed_pwd如果用户相等,则用户通过身份验证。

该协议将是不安全的,因为服务器存储的哈希值将是“密码等效的”;窃取服务器密码数据库的黑客将能够通过协议重放哈希以冒充任何用户。所以服务器仍然需要做加盐哈希它可以做的是将最昂贵的计算转移到客户端,以便服务器只需要计算一个快速的加盐哈希。

更好的协议是这样的:

  1. 客户端建立到服务器的经过身份验证的机密通道。(例如,具有有效服务器证书的 SSL 连接。)
  2. 客户端发送username到服务器
  3. 服务器查找相应的密码条目(username, client_salt, server_salt, hashed_pwd)(与坏协议相比,这是一个改变的步骤:好协议有两种盐,一种供客户端使用,一种供服务器使用。)
  4. 服务器发送client_salt给客户端。
  5. 客户端使用代价高昂的密码散列对其密码进行散列: client_hash = pwhash(client_salt, pwd)
  6. 客户端发送client_hash到服务器。
  7. 服务器计算server_hash = fast_salted_hash(server_salt, client_hash)fast_salted_hash函数可以是 HMAC 或 Blake2,以server_salt. (这是错误协议中不存在的附加步骤。)
  8. 服务器server_hash比较hashed_pwd如果用户相等,则用户通过身份验证。

通过在客户端对密码散列进行加盐处理,这意味着选择相同密码的用户将通过网络发送不同的散列,因此入侵一个客户端并获悉该客户端的黑客client_hash无法重放它以使用相同的用户身份登录密码。

通过仅存储 的加盐散列client_hash,我们可以防御窃取密码数据库的攻击者,就像在传统的密码散列中一样。