为什么加盐哈希对密码存储更安全?

信息安全 密码 密码学 哈希 彩虹桌
2021-08-14 22:02:04

我知道有很多关于加盐哈希的讨论,并且我理解其目的是使构建所有可能的哈希(通常最多 7 个字符)的彩虹表成为不可能。

我的理解是随机加盐值只是连接到密码哈希。为什么彩虹表不能用于密码哈希并忽略已知为随机盐哈希的前 X 位?

更新

感谢您的回复。我猜这个工作,目录(LDAP等)必须存储一个特定于每个用户的盐,或者看起来盐会“丢失”并且永远不会发生身份验证。

4个回答

它通常是这样工作的:

假设您的密码是“棒球”。我可以简单地将其原始存储,但任何获得我的数据库的人都会获得密码。所以我对它做一个 SHA1 哈希,然后得到这个:

$ echo -n baseball | sha1sum
a2c901c8c6dea98958c219f6f2d038c44dc5d362

从理论上讲,不可能反转 SHA1 哈希。但是去谷歌搜索那个确切的字符串,你就可以轻松恢复原始密码。

另外,如果数据库中的两个用户具有相同的密码,那么他们将具有相同的 SHA1 哈希。如果其中一个有密码提示,那么try "baseball"现在我知道两个用户的密码是什么了。

所以在我们散列它之前,我们预先添加一个唯一的字符串。不是秘密,只是一些独特的东西。怎么样WquZ012C所以现在我们正在对字符串进行哈希处理WquZ012Cbaseball这与此有关:

c5e635ec235a51e89f6ed7d4857afe58663d54f5

谷歌搜索该字符串没有任何结果(也许除了这个页面),所以现在我们开始做一些事情了。如果 person2 也使用“棒球”作为他的密码,我们使用不同的盐并获得不同的哈希值。

当然,为了测试你的密码,你必须知道盐是什么。所以我们必须把它存储在某个地方。大多数实现只是将它与哈希一起固定在那里,通常带有一些分隔符。如果您已openssl安装,请尝试此操作:

[tylerl ~]$ openssl passwd -1
Password: baseball
Verifying - Password: baseball
$1$oaagVya9$NMvf1IyubxEYvrZTRSLgk0

这为我们提供了使用标准crypt库的哈希值。所以我们的哈希是$1$oaagVya9$NMvf1IyubxEYvrZTRSLgk0:它实际上是由 . 分隔的 3 个部分$我将用空格替换分隔符以使其在视觉上更清晰:

$1$oaagVya9$NMvf1IyubxEYvrZTRSLgk0
 1 oaagVya9 NMvf1IyubxEYvrZTRSLgk0
  • 1表示“算法编号 1”,有点复杂,但使用 MD5。还有很多其他的更好,但这是我们的例子。
  • oaagVya9是我们的盐。用我们的哈希就在那里。
  • NMvf1IyubxEYvrZTRSLgk0是实际的 MD5 和,base64 编码。

如果我再次运行该过程,我会得到一个完全不同的哈希值和不同的盐。在这个例子中,大约有 10 14种方式来存储这个密码。所有这些都是为了密码“棒球”:

$1$9XsNo9.P$kTPuyvrHqsJJuCci3zLwL.
$1$nLEOCtx6$uSnz6PF8q3YuUhB3rLTC3/
$1$/jZJXTF3$OqDuk8T/cEIGpeKWfsamf.
$1$2lC.Cb/U$KR0jkhpeb1sz.UIqvfYOR.

但是,如果我故意指定要检查的盐,我会得到我的预期结果:

[tylerl ~]$ openssl passwd -1 -salt oaagVya9
Password: baseball
Verifying - Password: baseball
$1$oaagVya9$NMvf1IyubxEYvrZTRSLgk0

这就是我运行的测试以检查密码是否正确。为用户找到存储的哈希,找到保存的盐,使用保存的盐重新运行相同的哈希,检查结果是否与原始哈希匹配。

自己实现

需要明确的是,这篇文章不是实施指南。不要简单地给您的 MD5 加盐并称其为好。在当今的风险环境中,这还不够。相反,您将希望运行一个迭代过程,该过程会运行数千次散列函数。这已经在其他地方多次解释过,所以我不会在这里讨论“为什么”

有几个成熟且值得信赖的选项可以做到这一点:

  • cryptcrypt :我上面使用的函数是所有 Unix/Linux 操作系统内置的 unix 密码散列机制的旧变体原始(基于 DES)的版本非常不安全;甚至不考虑它。我展示的那个(基于MD5)更好,但今天仍然不应该使用。后来的变化,包括 SHA-256 和 SHA-512 的变化应该是合理的。所有最近的变体都实现了多轮哈希。

  • bcryptcrypt :上述函数调用的河豚版本利用河豚具有非常昂贵的密钥设置过程这一事实,并采用“成本”参数相应地增加密钥设置时间。

  • PBKDF2 : ("Password-based Key Derivation Function version 2") 创建用于从简单密码生成强加密密钥,这是此处列出的唯一实际具有 RFC的函数。运行可配置的轮数,每一轮它都会对密码加上前一轮的结果进行哈希处理。第一轮使用盐。值得注意的是,它最初的预期目的是创建强密钥,而不是存储密码,但目标的重叠使得这也是一个值得信赖的解决方案。如果您没有可用的库并且被迫从头开始实现某些东西,那么这是最简单且记录最好的选项。不过,显然,使用经过严格审查的库总是最好的。

  • scrypt:最近推出的系统,专门设计用于难以在专用硬件上实现。scrypt除了需要多轮哈希函数外,还有一个非常大的工作内存状态,以增加实现的RAM需求。虽然非常新且大部分未经证实,但它看起来至少与其他产品一样安全,并且可能是所有产品中最安全的。

从技术上讲,您仍然可以使用彩虹表来攻击加盐哈希。但仅在技术上。加盐哈希击败彩虹表攻击,不是通过添加加密魔法,而是通过成倍增加成功找到冲突所需的彩虹表的大小。

是的,你需要储存盐:)

它不是在哈希之后添加的。它是在散列之前添加的,因此每个盐的散列完全不同。

它不是

hash abcd = defg
store 123defg (for user with 123 salt) and 456defg (for user with 456 salt)  

它是

hash 123abcd = ghij 
hash 456abcd = klmn

对于密码哈希,您需要使用PBKDF2/RFC2898/PKCS5v2、Bcrypt 或 Scrypt 之类的东西,所有这些都允许您选择一定数量的迭代(“工作因素”),而不仅仅是一个。例如,PBKDF2 在内部使用HMAC密钥散列和众所周知的散列算法(通常是 SHA-512、SHA-256 或 SHA-1)进行多次迭代,最好是数万到数十万次迭代。基本上没有用户抱怨。

大量迭代的原因是为了减慢攻击者的速度,这反过来又减少了他们在给定时间段内可以通过的密钥空间,因此他们无法有效地攻击更好的密码。显然,无论离线攻击,“密码”和“P@$$w0rd”都会被破解。

你是对的,每一行(用户)都需要他们自己唯一生成的、密码随机的长盐。该盐以明文形式存储。

使用 PBKDF2、Bcrypt 或 Scrypt,我还建议将迭代次数(工作因子)也以明文形式存储在数据库中,因此很容易更改(就我个人而言,我使用了一些随机的迭代次数 - 如果它总是不同的话,然后就不再担心来自管理层或其他开发人员的“哦,不,一个微小的改变可能会搞砸一切 - 永远不要再改变这个”)

请注意,当使用 PBKDF2 进行密码散列时,永远不要要求比本机散列输出更大的输出 - 对于 SHA-1,这是 20 个字节,对于 SHA-512,这是 64 个字节。

给出具有两种不同盐的 PBKDF2 的实际示例:

(PBKDF2 HMAC密码盐迭代输出字节结果)

  • PBKDF2 HMAC-SHA-512 MyPass vA8u3v4qzCdb 131072 64
    • 2e3259bece6992f012966cbf5803103fdea7957ac20f3ec305d62994a3f4f088f26cc3889053fb59a4e3c282f55e9179695609ee1147cffae1455880993ef874
  • PBKDF2 HMAC-SHA-512 MyPass l6eZQVf7J65S 131072 64
    • 1018ad648096f7814bc2786972eb4091f6c36761a8262183c24b0f4d34abb48073ed2541ee273220915638b46ec14dfb2b23ad64c4aa12f97158340bdc12fc57