我可以对 SRP 和客户端加密使用相同的密码吗?

信息安全 加密 密钥生成 客户端 按键拉伸 srp
2021-08-19 12:42:20

假设一个不太受信任的服务器用于存储用户的机密数据(在客户端加密),并且两个任务 - 身份验证和加密/解密 - 都应该使用单个密码来完成。是否足以:

  1. 用缓慢的密钥推导功能和普通的盐来加强密码[1],让步k
  2. 用作SRP k“密码”,与服务器进行身份验证并接收密文;
  3. 解密密文以获得用于签名、加密等的实际密钥。

恶意服务器(低风险)必须对验证者或密文执行离线字典攻击才能检索k,而外部攻击者(风险较高)只能进行在线攻击(因为他既无权访问验证者也不是密文),除非他获得数据库的副本——这只会使他处于服务器的类似情况[2]

这个推理正确吗?有什么我没有预料到的缺陷吗?一些注意事项:

  1. 我说盐是“微不足道的”,因为它要么是:a)空的;b) 源自用户名;c) 随机,但服务器会将其提供给任何请求它的人。通常这是一个问题,但这里的密钥永远不会离开客户端机器,所以两个用户是否拥有相同的密钥并不重要。
  2. 我担心的一个问题是,攻击者可以创建一个针对特定用户的彩虹表,然后获取数据库并几乎立即以该用户身份登录,因为如果你有一个候选列表,强制验证者或密文会更快密钥而不是候选密码列表(在成为密钥之前仍然需要拉伸)。

    当然,如果攻击者具有这种计算能力(如果密码很弱),任何泄露的密文最终都会被破解,但如果服务器在这种情况下更强大,则可以避免未来的伤害(在了解 SRP 之前,我设计了一个更复杂的协议考虑到了这一点,但更加浪费并且有未解决的问题)。

  3. [对于我的特殊情况]所有这些都超出了问题的范围(假设已经弄清楚):所有通信都将通过 TLS 进行,将提供使用正确密钥和 2 因素身份验证的选项(但不是强制性的),客户端代码不会来自不太受信任的服务器,将使用 MAC,等等。
3个回答

作为健康谨慎的练习,我建议以下几点:

  • 当您k从用户密码派生密钥时,将其延长,并将其分成两部分。前半部分用于 SRP,后半部分用于加密。实现这一点的一种简单方法是k使用 SHA-256 进行散列,产生 256 位,然后可以将其拆分为两个精细的 128 位密钥。或者,使用具有可调输出大小的密钥派生函数,例如PBKDF2

    这里的重点是,我们希望避免在对称加密和 SRP 中执行的计算之间发生任何不必要的“交互”,无论多么难以置信。在两个子键之间插入一层单向性应该可以防止此类问题(假设 SHA-256 或多或少地表现得像随机预言机)。

  • 您进行加密是有原因的:然后您必须假设攻击者可能会在某个时候观察到对称加密的数据块。否则,加密的意义何在?但如果这样的窃听是可能的,那么盐一定是适当的。空盐不会削减它,因为所有用户都将使用相同的盐,从而允许攻击优化(即彩虹表)。

    现在有一个有趣的点,就是 SRP 本身包含了一些盐处理。仔细查看RFC 5054,它描述了如何将 SRP 集成到 TLS 握手中。在初始步骤中,客户端(在其 中ClientHello)发送用户的身份(I)。服务器响应一些消息,包括ServerKeyExchange包含(以及其他值) salt 的消息s因此,您在这里拥有“将盐发送给所有需要它的人的服务器”。

在 SRP 中,实际的密码处理是对x = SHA1(s | SHA1(I | ":" | P)). 这是一个加盐但迭代不足的密码散列函数。这是当前定义的 SRP 中的一个已知缺陷。但是,只要客户端和服务器同意,用强密码散列函数替换此操作将是微不足道的。

这将产生以下方案:

  • 在注册时,用户选择他的密码P,并生成随机盐s(例如,来自加密强 PRNG 的 128 位)。
  • P和开始s,应用一个强密码散列函数(例如bcrypt),产生一些中间值z,然后我们用 SHA-512 对其进行散列,得到 512 位。这 512 位被分成两个 256 位的一半;前半部分将是xSRP 的值,而后半部分(我们称之为y)将用于对称加密。
  • 登录时,客户端首先将用户名(I)发送到服务器,并获取 salt sx的相同计算y再次进行。x部分用于 SRP,即y解密存储在服务器上的 blob 的部分。

该方案结合了上面讨论的所有要点。密码到密钥派生使用具有良好随机盐的适当函数;在密码的两次使用之间有一个 SHA-512 的隔离层;与基本 SRP 相比,不需要额外的消息交换。实际上,我在这里建议的是加强在 SRP 中完成的密码散列,并额外提取密码派生密钥以满足您的对称加密需求。


请注意,虽然您可以先建立 SSL/TLS 隧道,然后在其中执行 SRP,但您也可以遵循 RFC 5054 路径并使用 SRP 进行 TLS 握手。这将需要控制客户端和服务器代码,包括 SRP 实现,因为我在这里建议修改密码到密钥的派生过程(即加强它)。相应地,客户端必须已经拥有所需的代码。在 Web-Javascript 上下文中,您首先需要一个“正常”的 SSL/TLS,尽管此时 SRP 的好处变得非常小。

让服务器将盐值发送给任何请求的人,这在 SRP 中是固有的,并不是一个严重的问题。盐不需要保密。将盐值保密并没有害处,但这只是次要目标。盐的要点是唯一性不要让对保密的无端渴望妨碍了独特性。

我将假设您的意思是使用密钥k(与 SRP 密码使用相同的值)对数据进行加密。您没有指定这一点,所以如果这不是您的想法,请编辑您的问题。

是的,这是一个合理的方案。

如果可能的话,我建议使用用户的用户名加上服务器的标识符(例如,域名)作为你的慢速密钥派生函数的盐。您还可以将其设为公开的且对每个用户不同的随机值。

彩虹表:彩虹表对攻击者帮助不大,因为创建彩虹表的成本与尝试一堆候选密码一样多。彩虹表仅在您进行多次攻击(例如,攻击多个用户)并且您想要摊销成本时才有帮助。如果您认为某个用户名很常见(例如,alice),则可以创建一个特定于alice(一次性计算)的彩虹表,然后用它来攻击多个服务器,希望有一个名为的帐户alice在所有这些服务器上。但是,如果您发现多个用户具有相同的用户名(大概是在多个不同的服务器上),这只会让您加快速度。我认为这在实践中并没有太大的威胁。如果您担心它,可以通过使用每个用户不同的随机值作为 salt 或使用用户的用户名和服务器标识符的组合作为 salt 来避免它。

其他:最大的弱点可能是客户端将获得解密密文的代码。如果这是一个专用的客户端应用程序,您可以将其构建到客户端程序的代码中。但是,如果客户端通过 Web 浏览器访问某个网站,并且解密代码由服务器作为 Javascript(例如)提供,那么您实际上并没有获得您认为的那么多安全性,因为恶意/妥协/欺骗服务器可以发送窃取用户密码的恶意 Javascript。

您可能需要考虑另一种方法。获取用户密码 p,应用盐并通过密钥推导将其推送以生成更长的密钥 k。然后将k分成两部分a和b。第一部分 a 用于加密数据,第二部分 b 用于对服务器进行身份验证。

这是否满足您的易用性和安全性要求?