从密码中提取盐分——它有多(不)安全?

Derive salt from password - How (in)secure is it?

我遇到了以下问题: 在 Java 应用程序中,我想将一些配置数据存储在加密的本地文件中。此文件可能用于机密数据,例如用户凭据。

该文件应该可以通过密码访问(而且只能使用密码)。 现在大多数值得信赖的人和参考实现都使用随机盐。我完全理解这是一个合理的选择。但是,如果我的应用程序终止并稍后启动,则随机盐将不再可用。此应用程序是独立的,因此无法将其他数据库用作盐库。

对于我的软件,用户只需输入密码(意思是:没有用户名、没有盐、没有最喜欢的动物或颜色)。

现在我的想法是从密码中提取盐(例如,使用 SHA-256 的前 16 个字节)。

我的问题是:

什么不是这个问题的目的:

要加密数据,需要 密钥 而不是密码。有 key-derivation-functions 从用户密码中获取密钥。

盐可用于密码散列,但不能用于加密数据。尽管有一个类似的加密概念,那里的随机值称为 IV 或 Nonce,并与加密数据一起存储。

你能做的最好的事情就是

  1. 使用带盐的密钥派生函数,从密码中获取密钥。
  2. 使用生成的密钥,您可以加密数据。
  3. 在这种情况下,盐可以存储在加密数据容器中(IV 已经存在),因此不需要全局数据库。

回答你原来的问题:从密码中推导出加盐否定了加盐的全部目的,它只是变成了一个更复杂的散列函数。

首先,如果可以的话,我强烈建议不要设计新颖的加密格式。很难正确地完成它们。如果您想要一种能够满足您描述的要求的加密格式,请参阅 JNCryptor, which is an implementation of the RNCryptor 格式。 RNCryptor 格式正是为解决这个问题而设计的,因此该规范是一个很好的信息来源,可以告诉您如果您不想直接使用它,您可以如何创建自己的格式。 (我是 RNCryptor 的作者。)

另请参阅 libsodium. It's a better encryption format than RNCryptor for various technical reasons, but it's a bit harder to install and use correctly. There are several Java bindings 了解 libsodium。

当您说 "of course, I did not implement crypto by myself," 时,这就是您在做的事情。加密方案不仅仅是 AES 代码。决定如何以一种新颖的方式生成盐是实施加密。有很多方法可以以简单的方式将安全原语(如盐)组合在一起,并使它们极度不安全。这就是为什么您想使用成熟的东西。

关键take-away 是您将盐与数据一起存储。我知道您说过这与储存盐无关,但您就是这样做的。最简单的方法是将盐粘贴到密文的开头并存储它。然后你只需从 header 中读取盐。同样,如果更方便的话,您可以将整个东西放在一个信封中。像 JSON:

这样简单的东西
{ "salt": "<base64-salt>",
  "data": "<base64-data>" }

这不是最有效的数据存储方式,但它简单、标准且安全。

记住,盐不是秘密。大家能看盐就好了


好的,关于正确操作的方法已经足够了。让我们进入您的实际问题。

你的加盐建议不是盐。它只是一个稍微不同的散列函数。 salt 的要点是,如果两次使用相同的密码(无意使用相同的密码),那么它们将具有不同的哈希值。你的计划失败了。如果我采用与您相同的方法,并选择与您相同的密码,则哈希值将相同。彩虹桌赢了。

解决这个问题的方法是使用静态盐,而不是修改后的哈希函数。您应该选择代表您的系统的盐。为此,我通常喜欢反向 DNS,因为它会导致唯一性。例如:"com.example.mygreatapp"。别人自然会选择"org.example.ourawesomedb"。你也可以选择一个很长的随机字符串,但重要的是唯一性,所以我喜欢反向 DNS。 (随机字符串往往让人认为盐是秘密,而盐不是秘密。)

这就是整个系统;只需选择您的系统独有的常量盐。 (如果您有用户名,则可以将用户名添加到盐中。这是构建确定性盐的标准方法。)

但对于文件存储,我绝不会那样做。