你在哪里存放你的盐串?
Posted
技术标签:
【中文标题】你在哪里存放你的盐串?【英文标题】:Where do you store your salt strings? 【发布时间】:2010-11-16 05:38:13 【问题描述】:在为数据库存储散列密码时,我总是使用正确的每个条目的盐字符串。根据我的需要,将盐存储在散列密码旁边的数据库中始终可以正常工作。
但是,有些人建议将盐与数据库分开存储。他们的论点是,如果数据库被入侵,攻击者仍然可以构建一个彩虹表,考虑到一个特定的盐字符串,以便一次破解一个帐户。如果这个帐号有管理员权限,那么他可能甚至不需要破解任何其他人。
从安全角度来看,将盐存放在不同的地方是否值得?考虑在同一台机器上具有服务器代码和数据库的 Web 应用程序。如果盐存储在该机器上的平面文件中,那么如果数据库被破坏,盐文件也将被破坏。
有没有推荐的解决方案?
【问题讨论】:
如果有一个地方可以存储攻击者无法获取的盐,那么你也应该在那里存储密码。但是为什么不为每个密码使用不同的盐呢? 他对每个密码都使用了不同的盐,jrockway。 你的盐有多大?您的盐应该足够大(32 位?),几乎不可能为它预先计算彩虹表。 @emddudley 这些天我一直习惯于使用 64 位整数作为盐,但我没有理由不能让它们更长。 PWDTK 的作者sourceforge.net/projects/pwdtknet,老实说,我不会担心,我只会将盐存储在与密码相同的数据库中。无论如何,您应该始终假设攻击者知道盐,因此您的重点应该是使用 LARGE CRYPTO-RANDOM salt 并执行足够的密钥拉伸(PBKDF2 中的迭代),以便为一种已知的盐制作一个彩虹表是不可行的。老实说,您试图通过将盐放在其他地方来实现的是“默默无闻的安全性”,当您看到另一台服务器可能出现故障时,通常没有任何好处。 【参考方案1】:如果您使用的库(或制作您自己的库)使用固定大小的字符串作为盐,那么您可以将盐和散列密码存储在同一字段中。然后,您将拆分存储的值以检索盐和散列密码以验证输入。
使用 10 个字符的盐和 40 个字符的固定散列大小,如下所示:
salt = "california"
passwd = "wp8tJ4Pr"
stored_passwd = salt + hash(passwd + salt)
salt = substr(stored_passwd, 0, 10)
hashed_passwd = substr(stored_passwd, 10, 40)
if hash(user_input + salt) == hashed_passwd:
print "password is verified"
由于盐的全部目的是防止使用预先计算的表(例如彩虹表)进行密码攻击,因此将盐与散列密码一起存储实际上是无害的。
【讨论】:
【参考方案2】:使用盐渍法防止彩虹表附着的原因。恶意用户以某种方式到达数据库并看到散列密码,获取最常见密码表,找到它们的散列值并在表中查找密码。
所以当用户发送密码时,我们将随机生成的盐添加到密码中。
userPassword + salt
然后我们将其传递给我们的哈希算法。
hash(userPassword+salt)
由于 salt 是随机生成的,userPassword+salt
成为一个随机值,绝对不是最常用的密码之一。所以恶意用户不会通过检查彩虹表来弄清楚使用了什么密码。
现在盐值被添加到哈希值之前,因为它会在用户登录时再次用于将传递的凭据与保存的凭据进行比较。
hash(userPassword+salt)=ashdjdaskhfjdkhfjdashadslkhfdsdh
这就是密码在 db:ashdjdaskhfjdkhfjdashadslkhfdsdh.salt
中的存储方式
现在,如果恶意用户看到这个,他可以找出密码,但这将花费大量时间。因为每个密码都会得到不同的盐。让我们恶意拥有 5000 个常用密码及其哈希表。
重要的一点,恶意用户不只有一张桌子。因为不同的算法太多了,所以恶意用户每个算法都会有5000个密码的hash值。
现在对于每个密码,假设他从第一个用户的密码开始,他会将该盐添加到 5000 个常用密码中,并为每个不同的算法创建一个新的彩虹表,以便仅找到一个密码。然后对于第二个用户的密码,他会看到不同的盐,他会计算新的彩虹表。甚至不能保证,用户的密码会在那些常见的密码列表中。
【讨论】:
【参考方案3】:盐的目的是使所有彩虹表都无用,并需要制作一组新的彩虹表。 猜一个字符串的时间与制作一张彩虹表的时间一样长。
例如,“密码”的 SHA-256 哈希是 5e88 4898 da28 0471 51d0 e56f 8dc6 2927 7360 3d0d 6aab bdd6 2a11 ef72 1d15 42d8
。
添加盐后,例如“badpassword”,要散列的新字符串为“passwordbadpassword”,由于雪崩效应,输出显着更改为457b f8b5 37f1 802e f9c8 2e46 b8d3 f8b5 721b 7cbb d485 f0bb e523 bfbe 73e6 58d6
。
通常情况下,盐只是与密码存储在同一个数据库中,因为如果一个数据库被黑客入侵,另一个数据库很可能也会被黑客入侵。
【讨论】:
【参考方案4】:基于 William Penberthy 的《开发 ASP.NET MVC 4 Web 应用程序》一书:
-
访问存储在单独数据库中的盐需要黑客破解两个
不同的数据库来访问盐和加盐密码。将它们存储在
与密码相同的表,甚至同一数据库的另一个表,将
这意味着当黑客获得对数据库的访问权限时,他们将同时访问
盐和密码哈希。因为安全包括进行黑客攻击的过程
进入系统太贵或太耗时,不值得,加倍的金额
黑客必须获得的访问权限应该会使系统更加安全。
易于使用是将盐保存在与
散列密码。您不必确保两个数据库始终可用
同时,并且始终保持同步。有盐的好处是最小的,如果
每个用户都有一个随机的盐,因为尽管它可能会发现个人的
密码更容易,破解密码所需的力量
系统整体会很高。在这个级别的讨论中,这确实是期望
是:保护密码。如果黑客获得了数据库的副本,您的
应用程序数据已被泄露。在这一点上,问题是减轻用户的
共享密码的潜在风险。
维护两个独立链接的数据库的需求非常广泛。诚然,它
增加了安全感,但它提供的唯一优势是它可以保护
密码,单个数据元素。如果数据库中的每个字段都是单独的
加密,并且使用相同的盐,存储它会更有意义
与数据分开,因为您的系统的基本安全性得到了增强。
【讨论】:
如果应用程序可以同时对两个数据库进行身份验证,那么如果攻击者破坏了应用程序代码,它是否与一个数据库基本相同?【参考方案5】:我将对此略有不同。
我总是将盐与加盐密码哈希混合在一起存储。
例如,我会将盐的前半部分放在密码的盐渍散列之前,将盐的后半部分放在密码的盐渍散列之后。应用程序知道这种设计,因此可以获取此数据,并获取盐和盐渍密码哈希。
我采用这种方法的理由:
如果密码/哈希数据被泄露并落入攻击者手中,攻击者将不会通过查看数据知道盐是什么。这样,攻击者实际上无法执行暴力攻击来获得与哈希匹配的密码,因为他不知道哈希的开头,也无法知道数据的哪些部分是盐的一部分,或者加盐密码哈希的一部分(除非他知道您的应用程序的身份验证逻辑)。
如果加盐密码散列按原样存储,则可以执行蛮力攻击以获得密码,当加盐和散列产生与加盐密码散列相同的数据。
但是,例如,即使加盐密码哈希按原样存储,但预先添加了一个随机字节,只要攻击者不知道第一个字节将被丢弃,这也会增加攻击难度。当您的应用程序用于对您的用户进行身份验证时,您的应用程序会知道丢弃数据的第一个字节。
结论..
1) 切勿以准确的形式存储您的身份验证应用程序使用的数据。
2) 如果可能,请对您的身份验证逻辑保密以增加安全性。
更进一步..
如果您无法对应用程序的身份验证逻辑保密 - 很多人都知道您的数据是如何存储在数据库中的。并假设您决定将加盐密码哈希与盐混合存储,其中一些盐在加盐密码散列之前,其余的盐在它后面。
在生成随机 salt 时,您还可以随机决定在 salted-password 哈希之前/之后存储的 salt 比例。
例如,您生成一个 512 字节的随机盐。您将盐附加到您的密码中,并获取您的加盐密码的 SHA-512 哈希。您还生成一个随机整数 200。然后存储 salt 的前 200 个字节,然后是 salted-password 哈希,然后是 salt 的其余部分。
当验证用户的密码输入时,您的应用程序将传递字符串,并假设数据的前 1 个字节是 salt 的前 1 个字节,然后是 salted-hash。此通行证将失败。应用程序将继续使用数据的前 2 个字节作为 salt 的前 2 个字节,并重复使用前 200 个字节作为 salt 的前 200 个字节后找到肯定的结果。如果密码错误,应用程序将继续尝试所有排列,直到找不到。
这种方法的优点:
提高了安全性 - 即使您的身份验证逻辑是已知的,但在编译时确切的逻辑是未知的。即使知道确切的逻辑,也几乎不可能执行暴力攻击。增加盐的长度将进一步提高安全性。
这种方法的缺点:
由于在运行时推断出确切的逻辑,因此这种方法非常占用 CPU。 salt 的长度越长,这种方法的 CPU 密集度就越高。
验证不正确的密码将涉及最高的 CPU 成本。这可能会对合法请求产生反作用,但会提高对攻击者的安全性。
这种方法可以通过多种方式实现,并且可以通过使用可变宽度的盐和/或加盐密码哈希来提高安全性。
【讨论】:
通过您的方法,您只是在散列过程中添加了一个秘密(应用盐的算法)。您可以通过在盐中额外添加 胡椒 来更轻松地添加这个秘密,我试图在我的 tutorial 中指出这一点。像 BCrypt 这样的现代哈希函数会自行应用盐,在每次迭代中使用原始盐,所以无论如何你都无法控制它。 @martinstoeckli 虽然您认为 BCrypt 自己应用盐是正确的,但盐 + 哈希的存储取决于您作为开发人员。因此,您可以轻松地在 salt+hash 中添加胡椒粉并将其持久化到数据库中。然后,在随后的检索中,您从数据库中读取值,去除辣椒值,并将剩余的值传递给 BCrypt。 @PeterToTheThird - 这会抵消辣椒的优势。辣椒添加了一个服务器端的秘密,并且只有在它保持秘密的情况下才有效(与盐相反)。一个典型的攻击是 SQL 注入,当有人获得了对数据库的访问权而不是对代码的访问权时,那么存储在数据库中的辣椒将毫无用处。大多数 BCrypt 实现会自动将盐添加到生成的散列值中,因此该值已经包含盐、成本因子、算法和散列。此字符串可以存储在 60 个字符长度的单个字段中。 补充一点,当使用 BCrypt 等“密钥强化”功能时,您无法控制盐的使用。但是,如果您想使用胡椒,您只需将胡椒附加到盐上,并将其用作“胡椒盐”来代替散列函数的“盐”输入。然后,“pepper”是一段合适的数据,它不存储在数据库中,但嵌入在身份验证代码中,或者存储在另一个安全位置。我从通用的角度处理了这个问题,使用 SHA-512 作为示例函数,但 BCrypt 等也可以以类似的方式使用。 @martinstoeckli - 是的,实际实现取决于您使用的哈希函数。显然,在实现身份验证逻辑时,您需要考虑哈希函数的参数和输出。归根结底,辣椒只是引入散列函数的另一个变量,它不存储在与盐和散列相同的位置。【参考方案6】:通常,它们被添加到散列之前并存储在同一字段中。
没有必要单独存储它们 - 关键是为每个密码使用随机盐,这样单个彩虹表就不能用于您的整个密码哈希集。使用随机盐,攻击者必须分别暴力破解每个哈希(或为所有可能的盐计算彩虹表 - 工作量大得多)。
如果您有一个更安全的存储位置,那么只将哈希存储在那里是有意义的。
【讨论】:
但是如果所有散列密码都泄露了,包括它们匹配的盐,会发生什么?这不是一样不安全吗? @mghaoui 但是,如果您想知道“密码”,您仍然必须为每种盐构建一个彩虹表,除非某些盐是相同的。【参考方案7】:彩虹表的关键在于它们是预先创建并大量分发以节省其他人的计算时间 - 动态生成彩虹表所需的时间与破解密码+盐组合所需的时间一样长直接(因为在生成彩虹表时所做的实际上是预先运行计算以强制哈希),因此通过知道盐某人可以“生成彩虹表”的论点是虚假的。
将盐存储在单独的文件中没有任何意义,只要它们是基于每个用户的 - 盐的目的只是为了使彩虹表不会破坏数据库。
【讨论】:
同意。您通过单独存储盐来保护的威胁模型是一个用户,该用户可以通过邪恶的方式以某种方式访问数据库中的盐,但不能访问哈希(在数据库中)。并且那个人将提前开始计算彩虹表,假设他以后能够找到散列。并非不可能,但也不值得为防御这种单一的攻击途径而付出工程努力。 不错的帖子,我也在想同样的事情。我从没想过每个用户一个盐,我认为一种盐对所有用户都有效。存储为由应用服务器加载的 XML 文件的盐呢?或者可能以某种方式硬编码到 servlet 中? @Jigzat - 如果您没有为每个用户提供单独的盐,那么加盐是毫无意义的。 salts 的重点是使破解哈希成为每个用户密码的单独任务;如果所有的盐都相同,那么情况并非如此。 @TomRitter 这不是唯一的情况。您假设所有密码都很复杂。一些攻击者可能会使用 salt 和 hash 并仅检查 10,000 个最常见的密码。这样他们就会得到相当数量的人。但是,如果他们无法访问盐,这类似于用户拥有更长更安全的密码。现在,salt 数据库在密码数据库被盗时保持安全的可能性有多大还有待商榷,但这是一个单独的问题。 @Amber,我相信 TomRitter 是正确的。单独存储盐意味着强制攻击者使用暴力攻击与更简单的字典攻击之间的区别。如果您知道盐,您可以在运行磨坊字典攻击期间附加它。如果您可以 100% 保护您的盐,您可以简单地使用相同的盐并强制攻击者暴力破解所有内容(即使对于使用“密码”作为密码的用户)。但是你能捍卫你的盐吗……可能不会。因此,不妨通过将其存储在哈希旁边并强制执行更严格的密码规则来减少故障点。以上是关于你在哪里存放你的盐串?的主要内容,如果未能解决你的问题,请参考以下文章