如何储存盐?

信息安全 密码 哈希
2021-08-23 21:20:53

如果您希望安全地存储用户密码,您至少需要执行以下操作:

$pwd=hash(hash($password) + salt)

然后,您存储$pwd在您的系统中,而不是真正的密码。我见过一些$pwd包含salt自身的情况。

我想知道盐是否应该单独存储,或者如果攻击者同时获取哈希值和盐是否可以。为什么?

4个回答

TL;DR - 您可以在没有任何形式的混淆或加密的情况下以纯文本形式存储盐,但不要只将其提供给任何想要它的人。


我们使用盐的原因是为了阻止预计算攻击,例如彩虹表这些攻击涉及创建散列及其明文的数据库,以便可以搜索散列并立即将其反转为明文。

例如*:

86f7e437faa5a7fce15d1ddcb9eaeaea377667b8 a
e9d71f5ee7c92d6dc9e92ffdad17b8bd49418f98 b
84a516841ba77a5b4648de2cd0dfcb30ea46dbb4 c
...
948291f2d6da8e32b007d5270a0a5d094a455a02 ZZZZZX
151bfc7ba4995bfa22c723ebe7921b6ddc6961bc ZZZZZY
18f30f1ba4c62e2b460e693306b39a0de27d747c ZZZZZZ

大多数表还包括常用密码列表:

5baa61e4c9b93f3f0682250b6cf8331b7ee68fd8 password
e38ad214943daad1d64c102faec29de4afe9da3d password1
b7a875fc1ea228b9061041b7cec4bd3c52ab3ce3 letmein
5cec175b165e3d5e62c9e13ce848ef6feac81bff qwerty123

*我在这里使用 SHA-1 作为示例,但稍后我会解释为什么这是一个坏主意。

因此,如果我的密码哈希是9272d183efd235a6803f595e19616c348c275055,那么在数据库中搜索它并找出明文是非常容易的bacon4因此,与其花几个小时破解哈希(好吧,在这种情况下,在一个像样的GPU上需要几分钟,但我们稍后会讨论这个),你会立即得到结果。

显然这对安全性不利!所以,我们使用盐。盐是与每个密码一起存储的随机唯一令牌。假设盐是5aP3v*4!1bN<x4i&3,哈希是9537340ced96de413e8534b542f38089c65edff3现在你的密码数据库没用了,因为没有人拥有包含该哈希的彩虹表。为每种可能的盐生成彩虹表在计算上是不可行的。

所以现在我们已经迫使坏人再次开始破解哈希。这种情况下,我用了不好的密码就很容易被破解,但总比他能在十分之一秒内查到的好!

现在,由于 salt 的目标只是防止创建预生成的数据库,因此不需要在数据库中对其进行加密或隐藏。您可以将其存储为纯文本。目标是迫使攻击者在获得数据库后必须破解哈希,而不是仅仅在彩虹表中查找它们。

但是,有一个警告。如果攻击者可以在侵入您的数据库之前悄悄地访问盐,例如通过一些脚本向任何要求它的人提供盐,他可以尽可能容易地为该盐生成彩虹表,如果没有的话。这意味着他可以默默地拿走您的管理员帐户的盐并生成一个漂亮的大彩虹表,然后侵入您的数据库并立即以管理员身份登录。这使您没有时间发现发生了违规行为,也没有时间采取措施防止损坏,例如更改管理员密码/锁定特权帐户。这并不意味着你应该隐藏你的盐或尝试加密它们,它只是意味着你应该设计你的系统,以便他们可以获取盐的唯一方法是侵入数据库。

另一个要考虑的想法是胡椒胡椒是第二种盐,在各个密码之间是恒定的,但不存储在数据库中。我们可以将它实现为H(salt + password + pepper),或者KDF(password + pepper, salt)作为密钥派生函数——我们稍后会讨论这些。这样的值可能存储在代码中。这意味着攻击者必须同时访问数据库和源代码(或 ASP .NET、CGI 等情况下的 webapp 二进制文件)才能尝试破解哈希。这个想法应该只用于补充其他安全措施。当您担心 SQL 注入攻击时,胡椒粉很有用,攻击者只能访问数据库,但随着人们转向参数化查询,这种模型(慢慢地)变得不那么常见. 正在使用参数化查询,对吗?有人争辩说,辣椒通过默默无闻构成安全,因为你只是在掩盖辣椒,这在某种程度上是正确的,但这并不是说这个想法没有价值。

现在我们处于这样一种情况,攻击者可以暴力破解每个单独的密码哈希,但不能再搜索彩虹表中的所有哈希并立即恢复明文密码。那么,我们现在如何防止暴力攻击呢?

现代显卡包括具有数百个内核的 GPU。每个核心都非常擅长数学,但不太擅长决策。它每秒可以执行数十亿次计算,但在执行需要复杂分支的操作​​时非常糟糕。加密哈希算法适合第一种类型的计算。因此,可以利用OpenCLCUDA等框架来大规模加速哈希算法的运行。运行oclHashcat使用不错的显卡,您每秒可以计算超过 10,000,000,000 个 MD5 哈希值。SHA-1 也不会慢很多。有些人拥有专用的 GPU 破解设备,其中包含 6 个或更多高端显卡,导致 MD5 的破解速度超过每秒 500 亿次哈希。让我把它放在上下文中:这样的系统可以在不到 4 分钟的时间内暴力破解 8 个字符的字母数字密码

显然,像 MD5 和 SHA-1 这样的哈希对于这种情况来说太快了。一种方法是执行加密哈希算法的数千次迭代:

hash = H(H(H(H(H(H(H(H(H(H(H(H(H(H(H(...H(password + salt) + salt) + salt) ... )

这会减慢哈希计算速度,但并不完美。一些人提倡使用SHA-2系列哈希,但这并没有提供太多额外的安全性。更可靠的方法是使用具有工作因子的密钥派生函数。这些函数需要一个密码、一个盐和一个工作因子。工作因素是一种根据您的硬件和安全要求扩展算法速度的方法:

hash = KDF(password, salt, workFactor)

两个最流行的 KDF 是PBKDF2bcryptPBKDF2 通过执行密钥HMAC的迭代(尽管它可以使用分组密码)来工作,而 bcrypt 通过计算和组合来自Blowfish分组密码的大量密文块来工作两者都做大致相同的工作。一种名为scrypt的新 bcrypt 变体的工作原理相同,但由于内存带宽限制,它引入了一种内存硬操作,使得破解 GPU 和FPGA场完全不可行。


更新:截至 2017 年 1 月,最先进的哈希算法是Argon2,它赢得了密码哈希竞赛。


希望这能让您对我们在存储密码时面临的问题有一个很好的概述,并回答您关于盐存储的问题。我强烈建议查看Jacco 答案底部的“感兴趣的链接”以供进一步阅读,以及这些链接:

盐并不意味着是秘密的,相反,盐通过确保每个使用的实例的哈希结果唯一而“起作用”。这是通过为每个计算的哈希选择不同的随机盐值来完成的。

当人们知道盐的意图时,它不会受到损害;攻击者仍然需要分别攻击每个哈希。因此,您可以简单地将盐与散列密码一起存储。

感兴趣的链接
如何安全地散列密码?
密码散列加盐+胡椒还是盐就够了?
Salt Generation 和开源软件

盐可以而且应该存储在加盐和散列密码旁边。此外,每个密码的盐值应该是唯一的。

其目的是使通过使用预先计算的密码哈希对表来攻击泄露的密码数据库变得不可行。

之所以有效,是因为攻击者只有在获得实际(硬化)密码后才知道盐;从而使任何预先计算的攻击成为不可能。(如果您不为每个密码使用唯一的盐,而是使用全局盐,则可能仍会使用预先计算的表 - 尽管必须专门为您的应用程序的盐预先计算它们。)

当您将盐存储在密码旁边以外的其他地方时,您可能会获得一些额外的安全性,但这几乎违背了目的:每次您想要验证密码时,您都需要盐和散列密码,所以它们有无论如何要非常“接近”(在建筑意义上)。

单独存储你的盐提供了一些额外的安全性,但并不多 - 毕竟,数据库应该是应用程序的最佳保护位置,所以如果攻击者可以访问数据库,他很可能可以访问任何其他数据。另一方面,您需要为每个用户使用不同的盐,这意味着您需要大量的盐——将其存储在数据库之外是不可行的。

有一种做法是使用对所有用户都相同的第二块盐(有时称为胡椒),并将其存储在数据库之外(可能在环境变量或某种配置文件中),因此攻击者不会通过简单的 SQL 注入访问它。这增加了一些安全性,但如上所述,不要期望太多,并使用 bcrypt 或其他一些慢散列方法来防御被盗的盐。