8

我正在设计一个 REST API,但在验证用户的安全性方面存在一些问题。对于身份验证,我不希望密码以纯文本形式通过网络发送。

为了绕过这个问题,我可以发送密码的 SHA-256 哈希(用户名为 salt),因此密码永远不会以纯文本形式发送。在我的数据库中,我将存储以下哈希值:SHA256(password + salt),如果两个哈希值匹配,我将进行比较。

此选项的问题是我将使用快速哈希算法计算哈希,并且盐不是随机的。

在安全方面,最佳实践是使用慢速签名算法,带有随机盐(如 bcrypt)。

慢速算法不是问题,我可以在客户端使用 bcrypt,但是对于 salt 我不知道该怎么做:

  • Bcrypt 需要一个定义大小的盐,所以我不能输入用户名
  • 如果我使用的是随机盐,客户端如何在计算密码哈希之前知道这个盐的值?

所以我可以看到 3 个选项,但没有一个令人满意:

  • 我以纯文本形式发送密码(我正在使用 SSL)并将 bcrypt 存储在数据库中 => 仍然容易受到中间人的攻击
  • 我使用 SHA256 并发送盐为用户名的哈希(仍在使用 SSL)=> 数据库中的哈希不太安全
  • 我使用 bcrypt,我有一个两步过程:我要求给定用户的 salt,然后发送该用户的哈希(仍然使用 ssl)=> 通过尝试使用其他用户名登录我可以获得他的 salt,而不是惊人的

有人有更好的解决方案或一些建议吗?

4

4 回答 4

5

我认为您可能在这里混淆/混淆了一些问题:

  • 如果您将哈希(密码 + 用户名)存储在服务器上,并且身份验证涉及发送哈希(密码 + 用户名),那么除了将密码存储在服务器上之外,您并没有真正取得任何更好的成就。仅长期存储哈希的目标是,如果您有数据泄露(即,攻击者获得对数据库的访问权限),他们仍然无法产生正确的值来进行身份验证。但是如果你做一个简单的比较,这仍然是一个问题。
  • hashing+salt的正确用法是:(1)服务器存储(Salt,hash(Password + Salt)的元组;(2)用户发送(声称的密码);(3)服务器计算哈希(声称的密码+盐);( 4) 如果 hash(claimed Password + Salt) == hash(Password + Salt),那么它们是真实的。这样,即使攻击者可以访问数据库,他们也无法生成这样的声明密码哈希(声称的密码+盐)是有效的。
  • 通过 SSL 发送明文密码并不是“明文”。根据@NullUserException 的评论,除非攻击者破坏了 SSL。只有服务器才能获得密码的值(假设服务器的公钥是有效的,这完全是另一回事)。

希望这可以帮助!

于 2012-10-24T15:43:40.367 回答
2

在客户端散列的方法有几个优点。其中之一是服务器永远不会获得真正的密码,因此如果服务器以任何方式受到损害,它仍然不会获得真正的密码。另一个是,如果您打算使用慢速散列,它可以减轻服务器端的负载。

但是,散列密码旨在保护您,以防数据库被破坏并且散列被盗。这意味着如果有人掌握了散列密码,他们仍然可以通过发送散列来冒充用户。这意味着,即使你在客户端散列,你仍然需要在服务器上重新散列。

另一个潜在的缺点是,这可能会疏远您未启用 JavaScript 的部分用户群。


为了解决您的观点:

Bcrypt 需要一个定义大小的盐,所以我不能输入用户名

不要将用户名用作盐。盐应该是唯一的,用户名(及其派生词)当然不是唯一的。我所说的唯一并不是指服务器唯一,而是在任何地方都是唯一的。请改用加密随机数。

如果我使用的是随机盐,客户端如何在计算密码哈希之前知道这个盐的值?

只需让服务器事先发送盐(随机数)。您也可以在客户端上执行此操作,但据我所知,Javascript 没有 CSPRNG,您仍然需要将 nonce 发送回服务器。

我以纯文本形式发送密码(我正在使用 SSL)并将 bcrypt 存储在数据库中 => 仍然容易受到中间人的攻击

SSL 旨在防止中间人攻击。除非它以某种方式损坏,否则这不会成为问题。

我使用 SHA256 并发送盐为用户名的哈希(仍在使用 SSL)=> 数据库中的哈希不太安全

不要将用户名用作盐。就像我之前说的,你必须在服务器端进行散列,不管你是否在客户端做了。

我使用bcrypt,我有一个两步过程:我要求给定用户的盐,然后发送这个用户的哈希(仍然使用ssl)=>通过尝试用另一个用户名登录我可以获得他的盐,不是很棒

确实不厉害。

于 2012-10-24T16:21:02.407 回答
1
  1. 使盐保持不变,假设使其成为用户名的哈希值。hash_val = HASH(HASH('username') + 'password')存储在服务器端也是如此。
  2. 对于身份验证,您的服务器会发送一个一次性随机值,即:nonce = HASH(RAND())
  3. 您的客户端根据输入凭据计算以下内容client_hash = HASH( nonce + HASH(HASH('username') + 'password'))并将其发送回服务器。
  4. 服务器执行相同的操作,比较结果哈希,并丢弃随机数。

通过这种方式,通过网络发送的哈希仅使用一次,并且您可以免受“重放”和 MITM 攻击。

另外,研究一下PBKDF 之类的东西来存储密码而不仅仅是散列,这使得暴力破解和彩虹表完全不切实际。这是我正在使用的 PHP 实现,因为它还没有在 PHP 中

于 2012-10-24T15:48:23.840 回答
0

如果可能,创建 API 密钥和密钥(API 用户名/密码,显然每个用户都是唯一的)以使用 API。您应该在您的站点界面中提供一个选项来激活/停用 API 访问以及重新生成 API 密钥和密钥的选项。在这里,用户将在此界面上看到 API 的 API/Secret key。

于 2012-10-24T16:01:58.630 回答