将盐存储在与哈希密码相同的字段中是否安全/明智?

信息安全 密码管理 数据库 贮存 氩2
2021-08-12 09:03:01

在我的应用程序中使用 Argon2 对密码进行哈希处理时,我注意到它会生成一个这样的字符串(例如密码“rabbit”):

$argon2i$v=19$m=65536,t=3,p=1$YOtX2//7NoD/owm8RZ8llw==$fPn4sPgkFAuBJo3M3UzcGss3dJysxLJdPdvojRF20ZE=

我的理解是,前面的一切p=都是参数,主体p=是盐,最后一部分是散列密码。

是否可以将其存储在 SQL 数据库的一个字段中(varchar(99)在这种情况下),还是应该将字符串分成其组成部分?我应该将盐与散列密码分开存储并将参数保存在代码中吗?

4个回答

您应该将其存储在单个字段中。不要试图把它分成几部分。

我知道这对于来自数据库背景的人来说可能看起来有点不直观,其中的作案方式是规范化数据并且使用序列化字符串被认为是一种丑陋的黑客攻击。但从安全实践来看,这是完全合理的。

为什么?因为单个开发人员需要操作的最小单元是完整的字符串。这就是应用程序需要传递给哈希库以检查提供的密码是否正确的内容。从开发人员的角度来看,哈希库可以是一个黑匣子,她不需要关心那些讨厌的部分的实际含义。

这是一件好事,因为大多数开发人员都是不完美的人(我知道,因为我自己也是)。如果你让他们把那根绳子分开,然后再试着把它们组合在一起,他们可能会把事情搞砸,比如给所有用户相同的盐或根本不加盐。

仅将参数存储在代码中也是一个坏主意。随着处理器变得更快,您可能希望在未来增加成本因素​​。在迁移阶段,不同的密码会有不同的成本因素。因此,您将需要有关密码级别的信息,了解用于散列它的成本因素。

您缺少的是哈希对原始数据起作用,减去原始字符串。当您想根据哈希验证字符串时,您可以获取提供的字符串,加上原始哈希数据(成本、盐等),然后生成一个新的哈希。如果新哈希与旧哈希匹配,则验证字符串(换句话说,字符串永远不会被解密,它会被重新哈希)。

吃盐并不能帮助您使用蛮力。盐是散列的重要组成部分,就像不倒翁是锁的一部分一样。如果你把任何一个都拿出来,没有人可以使用它。

分开存储是没有意义的。在大多数情况下,您必须重新组装完成的哈希以验证它。这就是为什么默认情况下所有组件都存储在一个方便的字符串中。

是的,您可以将其存储在单个字段中,并且许多数据库/应用程序将 salt+hash 存储在单个字段/文件等中。

最著名的是 Linux(它不是数据库),它使用以下格式将哈希存储在 /etc/shadow 文件中:

“$id$salt$hashed”,密码哈希的可打印形式,由 crypt (C) 生成,其中“$id”是使用的算法。(在 GNU/Linux 上,“$1$”代表 MD5,“$2a$”代表 Blowfish,“$2y$”代表 Blowfish(正确处理 8 位字符),“$5$”代表 SHA-256,“$6 $" 是 SHA-512,[4] 其他 Unix 可能有不同的值,例如 NetBSD。

(来源:https ://en.wikipedia.org/wiki/Passwd )

盐并不意味着是秘密的(或者至少不比哈希更秘密)。它的主要目的是使暴力攻击更加困难,因为攻击者必须为每个用户使用不同的盐。

但是您的问题更加细致入微-因为您不仅在询问盐,还询问了参数。诸如散列算法、迭代计数和盐之类的东西。无论如何,不​​要将它存储在代码中,它们仍然属于数据库。

想象一下,您有一群用户,并且您使用 SHA1 作为您的散列算法。因此,您的数据库字段将类似于SHA1:SALT:HASH

如果你想将你的数据库升级到 BCRYPT,你会怎么做?

通常,您会部署一些代码,以便在用户登录时验证密码,如果有效 - 您将使用更新的算法重新散列密码。现在用户的字段如下所示:BCRYPT:SALT:HASH

但是有些用户会在 SHA1 上,而其他用户会在 BCRYPT 上,因为这是在用户级别,所以您需要参数来告诉您的代码哪些用户是数据库中的哪些用户。

简而言之,将参数和散列存储在一个字段中是可以的,但无论出于何种原因(效率、更简单的代码等)将它们分开也是可以的。不好的是将其存储在您的代码中:)

TL:博士

Troy Hunt 最近发布了一个播客,建议不要以上述方式迁移到 BCRYPT,更有效的是简单地获取数据库中当前的所有 SHA1 哈希,并使用 BCRYPT 对它们进行哈希处理。

有效地 BCRYPT(SHA1(clear_password))

当用户登录时,您会

BCRYPT(SHA1(clear_password)) == <db_field>

这样,平台上的每个人都可以立即升级,并且您没有具有多种密码哈希格式的数据库。非常干净,非常好。

我认为这个想法很合理,但即使每个人都同时迁移,也不是瞬间完成的。除非您愿意在应用程序上接受一些停机时间(当您重新散列所有密码时),否则仍然会有一些用户使用 BCRYPT 和一些用户使用 SHA1 的时间间隔,因此您的数据库仍应存储散列算法的参数,您的代码将基于它执行。

从安全的角度来看,盐和散列密码存储在单个字段还是单独的字段中并不重要,尽管为了简单起见,我倾向于使用单个字段。将它们分开的唯一原因是如果您的应用程序代码需要将盐和密码分别传递给验证函数。如果您的应用程序代码只需要一个组合字符串,那么您不妨以这种方式存储它。

例如,旧的 ASP.NET Membership 将密码 hash 和 salt 拆分为两个 DB 字段,而新的 ASP.NET Identity 将 hash 和 salt 放在一个 DB 字段中,而新的函数已被修改以处理单个字符串作为输入和输出。