314

在存储之前对密码进行两次哈希处理是否比仅对密码进行一次哈希处理更安全或更安全?

我正在谈论的是这样做:

$hashed_password = hash(hash($plaintext_password));

而不仅仅是这个:

$hashed_password = hash($plaintext_password);

如果它不太安全,你能提供一个很好的解释(或一个链接)吗?

另外,使用的散列函数有什么不同吗?如果您混合 md5 和 sha1(例如)而不是重复相同的哈希函数,会有什么不同吗?

注意 1:当我说“双重哈希”时,我指的是对密码进行两次哈希处理,以使其更加模糊。我不是在谈论解决冲突的技术

注意 2:我知道我需要添加随机盐才能真正确保安全。问题是使用相同的算法进行两次散列是否有助于或损害散列。

4

16 回答 16

283

散列密码一次是不安全的

不,多个散列并不安全;它们是安全密码使用的重要组成部分。

迭代哈希会增加攻击者尝试其候选列表中每个密码的时间。您可以轻松地将攻击密码的时间从数小时增加到数年。

简单的迭代是不够的

仅仅将哈希输出链接到输入并不足以保证安全。迭代应该在保留密码熵的算法的上下文中进行。幸运的是,有几种已发布的算法已经过足够的审查,从而对其设计充满信心。

像 PBKDF2 这样的良好密钥派生算法将密码注入每一轮散列,从而减轻对散列输出冲突的担忧。PBKDF2 可按原样用于密码验证。Bcrypt 遵循加密步骤的密钥推导;这样,如果发现了一种快速逆转密钥派生的方法,攻击者仍然必须完成已知明文攻击。

如何破解密码

存储的密码需要保护免受离线攻击。如果密码没有加盐,它们可以通过预先计算的字典攻击(例如,使用彩虹表)来破解。否则,攻击者必须花时间计算每个密码的哈希值并查看它是否与存储的哈希值匹配。

并非所有密码的可能性都相同。攻击者可能会彻底搜索所有短密码,但他们知道,暴力破解成功的机会会随着每增加一个字符而急剧下降。相反,他们使用最可能密码的有序列表。它们以“password123”开头,然后逐渐变为不常用的密码。

假设一个攻击者列表很长,有 100 亿个候选者;还假设桌面系统每秒可以计算 100 万个哈希值。如果只使用一次迭代,攻击者可以测试她的整个列表不到三个小时。但如果只使用 2000 次迭代,那么这个时间会延长到将近 8 个月。要击败更复杂的攻击者(例如,能够下载可以利用其 GPU 功能的程序的攻击者),您需要更多的迭代。

多少才够?

要使用的迭代次数是安全性和用户体验之间的权衡。攻击者可以使用的专用硬件很便宜,但它仍然可以每秒执行数亿次迭代。攻击者系统的性能决定了在给定多次迭代的情况下破解密码需要多长时间。但是您的应用程序不太可能使用这种专用硬件。在不激怒用户的情况下,您可以执行多少次迭代取决于您的系统。

您可能可以让用户在身份验证期间额外等待 ¾ 秒左右。分析您的目标平台,并尽可能多地使用迭代。我测试过的平台(移动设备上的一个用户,或服务器平台上的多个用户)可以轻松地支持60,000 到 120,000 次迭代的PBKDF2,或者成本因子为 12 或 13 的bcrypt

更多背景

阅读 PKCS #5 以获取有关盐和迭代在散列中的作用的权威信息。尽管 PBKDF2 旨在从密码生成加密密钥,但它可以很好地作为密码验证的单向哈希。bcrypt 的每次迭代都比 SHA-2 哈希更昂贵,因此您可以使用更少的迭代,但想法是一样的。Bcrypt 还超越了大多数基于 PBKDF2 的解决方案,它使用派生密钥来加密众所周知的纯文本。生成的密文与一些元数据一起存储为“散列”。然而,没有什么能阻止你对 PBKDF2 做同样的事情。

以下是我在这个主题上写的其他答案:

于 2008-12-07T21:44:54.033 回答
238

对于那些说它是安全的人来说,他们通常是正确。对于特定问题,“双重”散列(或逻辑扩展,迭代散列函数)如果做得对,是绝对安全的。

对于那些说它不安全的人,他们在这种情况下是正确的。问题中发布的代码不安全。让我们谈谈为什么:

$hashed_password1 = md5( md5( plaintext_password ) );
$hashed_password2 = md5( plaintext_password );

我们关注哈希函数的两个基本属性:

  1. Pre-Image Resistance - 给定一个散列$h,应该很难找到$m这样的消息$h === hash($m)

  2. Second-Pre-Image Resistance - 给定一条消息$m1,应该很难找到不同的消息$m2,这样hash($m1) === hash($m2)

  3. 碰撞阻力- 应该很难找到一对($m1, $m2)这样的消息hash($m1) === hash($m2)(请注意,这类似于 Second-Pre-Image 阻力,但不同之处在于攻击者可以控制这两个消息)......

对于密码的存储,我们真正关心的是Pre-Image Resistance。另外两个将没有实际意义,因为$m1我们试图保护用户的密码。因此,如果攻击者已经拥有它,那么哈希就没有什么可以保护的了……

免责声明

接下来的一切都是基于我们所关心的前提是Pre-Image Resistance。散列函数的其他两个基本属性可能不会(并且通常不会)以相同的方式成立。所以这篇文章中的结论只适用于使用哈希函数存储密码的情况。它们一般不适用...

让我们开始吧

为了便于讨论,让我们发明自己的哈希函数:

function ourHash($input) {
    $result = 0;
    for ($i = 0; $i < strlen($input); $i++) {
        $result += ord($input[$i]);
    }
    return (string) ($result % 256);
}

现在应该很明显这个哈希函数做了什么。它将输入的每个字符的 ASCII 值相加,然后将该结果与 256 取模。

所以让我们测试一下:

var_dump(
    ourHash('abc'), // string(2) "38"
    ourHash('def'), // string(2) "47"
    ourHash('hij'), // string(2) "59"
    ourHash('klm')  // string(2) "68"
);

现在,让我们看看如果我们围绕一个函数运行几次会发生什么:

$tests = array(
    "abc",
    "def",
    "hij",
    "klm",
);

foreach ($tests as $test) {
    $hash = $test;
    for ($i = 0; $i < 100; $i++) {
        $hash = ourHash($hash);
    }
    echo "Hashing $test => $hash\n";
}

输出:

Hashing abc => 152
Hashing def => 152
Hashing hij => 155
Hashing klm => 155

嗯,哇。我们已经产生了碰撞!!!让我们试着看看为什么:

这是对每个可能的哈希输出的字符串进行哈希处理的输出:

Hashing 0 => 48
Hashing 1 => 49
Hashing 2 => 50
Hashing 3 => 51
Hashing 4 => 52
Hashing 5 => 53
Hashing 6 => 54
Hashing 7 => 55
Hashing 8 => 56
Hashing 9 => 57
Hashing 10 => 97
Hashing 11 => 98
Hashing 12 => 99
Hashing 13 => 100
Hashing 14 => 101
Hashing 15 => 102
Hashing 16 => 103
Hashing 17 => 104
Hashing 18 => 105
Hashing 19 => 106
Hashing 20 => 98
Hashing 21 => 99
Hashing 22 => 100
Hashing 23 => 101
Hashing 24 => 102
Hashing 25 => 103
Hashing 26 => 104
Hashing 27 => 105
Hashing 28 => 106
Hashing 29 => 107
Hashing 30 => 99
Hashing 31 => 100
Hashing 32 => 101
Hashing 33 => 102
Hashing 34 => 103
Hashing 35 => 104
Hashing 36 => 105
Hashing 37 => 106
Hashing 38 => 107
Hashing 39 => 108
Hashing 40 => 100
Hashing 41 => 101
Hashing 42 => 102
Hashing 43 => 103
Hashing 44 => 104
Hashing 45 => 105
Hashing 46 => 106
Hashing 47 => 107
Hashing 48 => 108
Hashing 49 => 109
Hashing 50 => 101
Hashing 51 => 102
Hashing 52 => 103
Hashing 53 => 104
Hashing 54 => 105
Hashing 55 => 106
Hashing 56 => 107
Hashing 57 => 108
Hashing 58 => 109
Hashing 59 => 110
Hashing 60 => 102
Hashing 61 => 103
Hashing 62 => 104
Hashing 63 => 105
Hashing 64 => 106
Hashing 65 => 107
Hashing 66 => 108
Hashing 67 => 109
Hashing 68 => 110
Hashing 69 => 111
Hashing 70 => 103
Hashing 71 => 104
Hashing 72 => 105
Hashing 73 => 106
Hashing 74 => 107
Hashing 75 => 108
Hashing 76 => 109
Hashing 77 => 110
Hashing 78 => 111
Hashing 79 => 112
Hashing 80 => 104
Hashing 81 => 105
Hashing 82 => 106
Hashing 83 => 107
Hashing 84 => 108
Hashing 85 => 109
Hashing 86 => 110
Hashing 87 => 111
Hashing 88 => 112
Hashing 89 => 113
Hashing 90 => 105
Hashing 91 => 106
Hashing 92 => 107
Hashing 93 => 108
Hashing 94 => 109
Hashing 95 => 110
Hashing 96 => 111
Hashing 97 => 112
Hashing 98 => 113
Hashing 99 => 114
Hashing 100 => 145
Hashing 101 => 146
Hashing 102 => 147
Hashing 103 => 148
Hashing 104 => 149
Hashing 105 => 150
Hashing 106 => 151
Hashing 107 => 152
Hashing 108 => 153
Hashing 109 => 154
Hashing 110 => 146
Hashing 111 => 147
Hashing 112 => 148
Hashing 113 => 149
Hashing 114 => 150
Hashing 115 => 151
Hashing 116 => 152
Hashing 117 => 153
Hashing 118 => 154
Hashing 119 => 155
Hashing 120 => 147
Hashing 121 => 148
Hashing 122 => 149
Hashing 123 => 150
Hashing 124 => 151
Hashing 125 => 152
Hashing 126 => 153
Hashing 127 => 154
Hashing 128 => 155
Hashing 129 => 156
Hashing 130 => 148
Hashing 131 => 149
Hashing 132 => 150
Hashing 133 => 151
Hashing 134 => 152
Hashing 135 => 153
Hashing 136 => 154
Hashing 137 => 155
Hashing 138 => 156
Hashing 139 => 157
Hashing 140 => 149
Hashing 141 => 150
Hashing 142 => 151
Hashing 143 => 152
Hashing 144 => 153
Hashing 145 => 154
Hashing 146 => 155
Hashing 147 => 156
Hashing 148 => 157
Hashing 149 => 158
Hashing 150 => 150
Hashing 151 => 151
Hashing 152 => 152
Hashing 153 => 153
Hashing 154 => 154
Hashing 155 => 155
Hashing 156 => 156
Hashing 157 => 157
Hashing 158 => 158
Hashing 159 => 159
Hashing 160 => 151
Hashing 161 => 152
Hashing 162 => 153
Hashing 163 => 154
Hashing 164 => 155
Hashing 165 => 156
Hashing 166 => 157
Hashing 167 => 158
Hashing 168 => 159
Hashing 169 => 160
Hashing 170 => 152
Hashing 171 => 153
Hashing 172 => 154
Hashing 173 => 155
Hashing 174 => 156
Hashing 175 => 157
Hashing 176 => 158
Hashing 177 => 159
Hashing 178 => 160
Hashing 179 => 161
Hashing 180 => 153
Hashing 181 => 154
Hashing 182 => 155
Hashing 183 => 156
Hashing 184 => 157
Hashing 185 => 158
Hashing 186 => 159
Hashing 187 => 160
Hashing 188 => 161
Hashing 189 => 162
Hashing 190 => 154
Hashing 191 => 155
Hashing 192 => 156
Hashing 193 => 157
Hashing 194 => 158
Hashing 195 => 159
Hashing 196 => 160
Hashing 197 => 161
Hashing 198 => 162
Hashing 199 => 163
Hashing 200 => 146
Hashing 201 => 147
Hashing 202 => 148
Hashing 203 => 149
Hashing 204 => 150
Hashing 205 => 151
Hashing 206 => 152
Hashing 207 => 153
Hashing 208 => 154
Hashing 209 => 155
Hashing 210 => 147
Hashing 211 => 148
Hashing 212 => 149
Hashing 213 => 150
Hashing 214 => 151
Hashing 215 => 152
Hashing 216 => 153
Hashing 217 => 154
Hashing 218 => 155
Hashing 219 => 156
Hashing 220 => 148
Hashing 221 => 149
Hashing 222 => 150
Hashing 223 => 151
Hashing 224 => 152
Hashing 225 => 153
Hashing 226 => 154
Hashing 227 => 155
Hashing 228 => 156
Hashing 229 => 157
Hashing 230 => 149
Hashing 231 => 150
Hashing 232 => 151
Hashing 233 => 152
Hashing 234 => 153
Hashing 235 => 154
Hashing 236 => 155
Hashing 237 => 156
Hashing 238 => 157
Hashing 239 => 158
Hashing 240 => 150
Hashing 241 => 151
Hashing 242 => 152
Hashing 243 => 153
Hashing 244 => 154
Hashing 245 => 155
Hashing 246 => 156
Hashing 247 => 157
Hashing 248 => 158
Hashing 249 => 159
Hashing 250 => 151
Hashing 251 => 152
Hashing 252 => 153
Hashing 253 => 154
Hashing 254 => 155
Hashing 255 => 156

注意数字更高的趋势。事实证明这是我们的死路一条。运行哈希 4 次($hash = ourHash($hash)`,对于每个元素)最终给我们:

Hashing 0 => 153
Hashing 1 => 154
Hashing 2 => 155
Hashing 3 => 156
Hashing 4 => 157
Hashing 5 => 158
Hashing 6 => 150
Hashing 7 => 151
Hashing 8 => 152
Hashing 9 => 153
Hashing 10 => 157
Hashing 11 => 158
Hashing 12 => 150
Hashing 13 => 154
Hashing 14 => 155
Hashing 15 => 156
Hashing 16 => 157
Hashing 17 => 158
Hashing 18 => 150
Hashing 19 => 151
Hashing 20 => 158
Hashing 21 => 150
Hashing 22 => 154
Hashing 23 => 155
Hashing 24 => 156
Hashing 25 => 157
Hashing 26 => 158
Hashing 27 => 150
Hashing 28 => 151
Hashing 29 => 152
Hashing 30 => 150
Hashing 31 => 154
Hashing 32 => 155
Hashing 33 => 156
Hashing 34 => 157
Hashing 35 => 158
Hashing 36 => 150
Hashing 37 => 151
Hashing 38 => 152
Hashing 39 => 153
Hashing 40 => 154
Hashing 41 => 155
Hashing 42 => 156
Hashing 43 => 157
Hashing 44 => 158
Hashing 45 => 150
Hashing 46 => 151
Hashing 47 => 152
Hashing 48 => 153
Hashing 49 => 154
Hashing 50 => 155
Hashing 51 => 156
Hashing 52 => 157
Hashing 53 => 158
Hashing 54 => 150
Hashing 55 => 151
Hashing 56 => 152
Hashing 57 => 153
Hashing 58 => 154
Hashing 59 => 155
Hashing 60 => 156
Hashing 61 => 157
Hashing 62 => 158
Hashing 63 => 150
Hashing 64 => 151
Hashing 65 => 152
Hashing 66 => 153
Hashing 67 => 154
Hashing 68 => 155
Hashing 69 => 156
Hashing 70 => 157
Hashing 71 => 158
Hashing 72 => 150
Hashing 73 => 151
Hashing 74 => 152
Hashing 75 => 153
Hashing 76 => 154
Hashing 77 => 155
Hashing 78 => 156
Hashing 79 => 157
Hashing 80 => 158
Hashing 81 => 150
Hashing 82 => 151
Hashing 83 => 152
Hashing 84 => 153
Hashing 85 => 154
Hashing 86 => 155
Hashing 87 => 156
Hashing 88 => 157
Hashing 89 => 158
Hashing 90 => 150
Hashing 91 => 151
Hashing 92 => 152
Hashing 93 => 153
Hashing 94 => 154
Hashing 95 => 155
Hashing 96 => 156
Hashing 97 => 157
Hashing 98 => 158
Hashing 99 => 150
Hashing 100 => 154
Hashing 101 => 155
Hashing 102 => 156
Hashing 103 => 157
Hashing 104 => 158
Hashing 105 => 150
Hashing 106 => 151
Hashing 107 => 152
Hashing 108 => 153
Hashing 109 => 154
Hashing 110 => 155
Hashing 111 => 156
Hashing 112 => 157
Hashing 113 => 158
Hashing 114 => 150
Hashing 115 => 151
Hashing 116 => 152
Hashing 117 => 153
Hashing 118 => 154
Hashing 119 => 155
Hashing 120 => 156
Hashing 121 => 157
Hashing 122 => 158
Hashing 123 => 150
Hashing 124 => 151
Hashing 125 => 152
Hashing 126 => 153
Hashing 127 => 154
Hashing 128 => 155
Hashing 129 => 156
Hashing 130 => 157
Hashing 131 => 158
Hashing 132 => 150
Hashing 133 => 151
Hashing 134 => 152
Hashing 135 => 153
Hashing 136 => 154
Hashing 137 => 155
Hashing 138 => 156
Hashing 139 => 157
Hashing 140 => 158
Hashing 141 => 150
Hashing 142 => 151
Hashing 143 => 152
Hashing 144 => 153
Hashing 145 => 154
Hashing 146 => 155
Hashing 147 => 156
Hashing 148 => 157
Hashing 149 => 158
Hashing 150 => 150
Hashing 151 => 151
Hashing 152 => 152
Hashing 153 => 153
Hashing 154 => 154
Hashing 155 => 155
Hashing 156 => 156
Hashing 157 => 157
Hashing 158 => 158
Hashing 159 => 159
Hashing 160 => 151
Hashing 161 => 152
Hashing 162 => 153
Hashing 163 => 154
Hashing 164 => 155
Hashing 165 => 156
Hashing 166 => 157
Hashing 167 => 158
Hashing 168 => 159
Hashing 169 => 151
Hashing 170 => 152
Hashing 171 => 153
Hashing 172 => 154
Hashing 173 => 155
Hashing 174 => 156
Hashing 175 => 157
Hashing 176 => 158
Hashing 177 => 159
Hashing 178 => 151
Hashing 179 => 152
Hashing 180 => 153
Hashing 181 => 154
Hashing 182 => 155
Hashing 183 => 156
Hashing 184 => 157
Hashing 185 => 158
Hashing 186 => 159
Hashing 187 => 151
Hashing 188 => 152
Hashing 189 => 153
Hashing 190 => 154
Hashing 191 => 155
Hashing 192 => 156
Hashing 193 => 157
Hashing 194 => 158
Hashing 195 => 159
Hashing 196 => 151
Hashing 197 => 152
Hashing 198 => 153
Hashing 199 => 154
Hashing 200 => 155
Hashing 201 => 156
Hashing 202 => 157
Hashing 203 => 158
Hashing 204 => 150
Hashing 205 => 151
Hashing 206 => 152
Hashing 207 => 153
Hashing 208 => 154
Hashing 209 => 155
Hashing 210 => 156
Hashing 211 => 157
Hashing 212 => 158
Hashing 213 => 150
Hashing 214 => 151
Hashing 215 => 152
Hashing 216 => 153
Hashing 217 => 154
Hashing 218 => 155
Hashing 219 => 156
Hashing 220 => 157
Hashing 221 => 158
Hashing 222 => 150
Hashing 223 => 151
Hashing 224 => 152
Hashing 225 => 153
Hashing 226 => 154
Hashing 227 => 155
Hashing 228 => 156
Hashing 229 => 157
Hashing 230 => 158
Hashing 231 => 150
Hashing 232 => 151
Hashing 233 => 152
Hashing 234 => 153
Hashing 235 => 154
Hashing 236 => 155
Hashing 237 => 156
Hashing 238 => 157
Hashing 239 => 158
Hashing 240 => 150
Hashing 241 => 151
Hashing 242 => 152
Hashing 243 => 153
Hashing 244 => 154
Hashing 245 => 155
Hashing 246 => 156
Hashing 247 => 157
Hashing 248 => 158
Hashing 249 => 159
Hashing 250 => 151
Hashing 251 => 152
Hashing 252 => 153
Hashing 253 => 154
Hashing 254 => 155
Hashing 255 => 156

我们已经将自己缩小到 8 个值......这很糟糕......我们的原始函数映射S(∞)S(256). 也就是说,我们创建了一个映射$input$output.

由于我们有一个满射函数,我们不能保证任何输入子集的映射都不会发生冲突(实际上,实际上它们会发生冲突)。

这就是这里发生的事情!我们的功能很糟糕,但这不是它起作用的原因(这就是它工作得如此迅速和如此完整的原因)。

同样的事情发生在MD5. 它映射S(∞)S(2^128). 由于无法保证 runningMD5(S(output))将是Injective,这意味着它不会发生冲突。

TL/DR 部分

因此,由于md5直接将输出反馈回会产生碰撞,因此每次迭代都会增加碰撞的机会。然而,这是一个线性增加,这意味着虽然结果集2^128减少了,但它并没有显着减少到足以成为关键缺陷的速度。

所以,

$output = md5($input); // 2^128 possibilities
$output = md5($output); // < 2^128 possibilities
$output = md5($output); // < 2^128 possibilities
$output = md5($output); // < 2^128 possibilities
$output = md5($output); // < 2^128 possibilities

你迭代的次数越多,减少的越多。

修复

对我们来说幸运的是,有一个简单的方法可以解决这个问题:将一些东西反馈到进一步的迭代中:

$output = md5($input); // 2^128 possibilities
$output = md5($input . $output); // 2^128 possibilities
$output = md5($input . $output); // 2^128 possibilities
$output = md5($input . $output); // 2^128 possibilities
$output = md5($input . $output); // 2^128 possibilities    

请注意,对于每个单独的值,进一步的迭代不是 2^128 $input。这意味着我们可能能够生成$input仍然沿线碰撞的值(因此将在远低于2^128可能输出的情况下稳定或产生共鸣)。但是一般情况下$input仍然与单轮一样强大。

等等,是吗?ourHash()让我们用我们的函数来测试一下。切换到$hash = ourHash($input . $hash);, 进行 100 次迭代:

Hashing 0 => 201
Hashing 1 => 212
Hashing 2 => 199
Hashing 3 => 201
Hashing 4 => 203
Hashing 5 => 205
Hashing 6 => 207
Hashing 7 => 209
Hashing 8 => 211
Hashing 9 => 204
Hashing 10 => 251
Hashing 11 => 147
Hashing 12 => 251
Hashing 13 => 148
Hashing 14 => 253
Hashing 15 => 0
Hashing 16 => 1
Hashing 17 => 2
Hashing 18 => 161
Hashing 19 => 163
Hashing 20 => 147
Hashing 21 => 251
Hashing 22 => 148
Hashing 23 => 253
Hashing 24 => 0
Hashing 25 => 1
Hashing 26 => 2
Hashing 27 => 161
Hashing 28 => 163
Hashing 29 => 8
Hashing 30 => 251
Hashing 31 => 148
Hashing 32 => 253
Hashing 33 => 0
Hashing 34 => 1
Hashing 35 => 2
Hashing 36 => 161
Hashing 37 => 163
Hashing 38 => 8
Hashing 39 => 4
Hashing 40 => 148
Hashing 41 => 253
Hashing 42 => 0
Hashing 43 => 1
Hashing 44 => 2
Hashing 45 => 161
Hashing 46 => 163
Hashing 47 => 8
Hashing 48 => 4
Hashing 49 => 9
Hashing 50 => 253
Hashing 51 => 0
Hashing 52 => 1
Hashing 53 => 2
Hashing 54 => 161
Hashing 55 => 163
Hashing 56 => 8
Hashing 57 => 4
Hashing 58 => 9
Hashing 59 => 11
Hashing 60 => 0
Hashing 61 => 1
Hashing 62 => 2
Hashing 63 => 161
Hashing 64 => 163
Hashing 65 => 8
Hashing 66 => 4
Hashing 67 => 9
Hashing 68 => 11
Hashing 69 => 4
Hashing 70 => 1
Hashing 71 => 2
Hashing 72 => 161
Hashing 73 => 163
Hashing 74 => 8
Hashing 75 => 4
Hashing 76 => 9
Hashing 77 => 11
Hashing 78 => 4
Hashing 79 => 3
Hashing 80 => 2
Hashing 81 => 161
Hashing 82 => 163
Hashing 83 => 8
Hashing 84 => 4
Hashing 85 => 9
Hashing 86 => 11
Hashing 87 => 4
Hashing 88 => 3
Hashing 89 => 17
Hashing 90 => 161
Hashing 91 => 163
Hashing 92 => 8
Hashing 93 => 4
Hashing 94 => 9
Hashing 95 => 11
Hashing 96 => 4
Hashing 97 => 3
Hashing 98 => 17
Hashing 99 => 13
Hashing 100 => 246
Hashing 101 => 248
Hashing 102 => 49
Hashing 103 => 44
Hashing 104 => 255
Hashing 105 => 198
Hashing 106 => 43
Hashing 107 => 51
Hashing 108 => 202
Hashing 109 => 2
Hashing 110 => 248
Hashing 111 => 49
Hashing 112 => 44
Hashing 113 => 255
Hashing 114 => 198
Hashing 115 => 43
Hashing 116 => 51
Hashing 117 => 202
Hashing 118 => 2
Hashing 119 => 51
Hashing 120 => 49
Hashing 121 => 44
Hashing 122 => 255
Hashing 123 => 198
Hashing 124 => 43
Hashing 125 => 51
Hashing 126 => 202
Hashing 127 => 2
Hashing 128 => 51
Hashing 129 => 53
Hashing 130 => 44
Hashing 131 => 255
Hashing 132 => 198
Hashing 133 => 43
Hashing 134 => 51
Hashing 135 => 202
Hashing 136 => 2
Hashing 137 => 51
Hashing 138 => 53
Hashing 139 => 55
Hashing 140 => 255
Hashing 141 => 198
Hashing 142 => 43
Hashing 143 => 51
Hashing 144 => 202
Hashing 145 => 2
Hashing 146 => 51
Hashing 147 => 53
Hashing 148 => 55
Hashing 149 => 58
Hashing 150 => 198
Hashing 151 => 43
Hashing 152 => 51
Hashing 153 => 202
Hashing 154 => 2
Hashing 155 => 51
Hashing 156 => 53
Hashing 157 => 55
Hashing 158 => 58
Hashing 159 => 0
Hashing 160 => 43
Hashing 161 => 51
Hashing 162 => 202
Hashing 163 => 2
Hashing 164 => 51
Hashing 165 => 53
Hashing 166 => 55
Hashing 167 => 58
Hashing 168 => 0
Hashing 169 => 209
Hashing 170 => 51
Hashing 171 => 202
Hashing 172 => 2
Hashing 173 => 51
Hashing 174 => 53
Hashing 175 => 55
Hashing 176 => 58
Hashing 177 => 0
Hashing 178 => 209
Hashing 179 => 216
Hashing 180 => 202
Hashing 181 => 2
Hashing 182 => 51
Hashing 183 => 53
Hashing 184 => 55
Hashing 185 => 58
Hashing 186 => 0
Hashing 187 => 209
Hashing 188 => 216
Hashing 189 => 219
Hashing 190 => 2
Hashing 191 => 51
Hashing 192 => 53
Hashing 193 => 55
Hashing 194 => 58
Hashing 195 => 0
Hashing 196 => 209
Hashing 197 => 216
Hashing 198 => 219
Hashing 199 => 220
Hashing 200 => 248
Hashing 201 => 49
Hashing 202 => 44
Hashing 203 => 255
Hashing 204 => 198
Hashing 205 => 43
Hashing 206 => 51
Hashing 207 => 202
Hashing 208 => 2
Hashing 209 => 51
Hashing 210 => 49
Hashing 211 => 44
Hashing 212 => 255
Hashing 213 => 198
Hashing 214 => 43
Hashing 215 => 51
Hashing 216 => 202
Hashing 217 => 2
Hashing 218 => 51
Hashing 219 => 53
Hashing 220 => 44
Hashing 221 => 255
Hashing 222 => 198
Hashing 223 => 43
Hashing 224 => 51
Hashing 225 => 202
Hashing 226 => 2
Hashing 227 => 51
Hashing 228 => 53
Hashing 229 => 55
Hashing 230 => 255
Hashing 231 => 198
Hashing 232 => 43
Hashing 233 => 51
Hashing 234 => 202
Hashing 235 => 2
Hashing 236 => 51
Hashing 237 => 53
Hashing 238 => 55
Hashing 239 => 58
Hashing 240 => 198
Hashing 241 => 43
Hashing 242 => 51
Hashing 243 => 202
Hashing 244 => 2
Hashing 245 => 51
Hashing 246 => 53
Hashing 247 => 55
Hashing 248 => 58
Hashing 249 => 0
Hashing 250 => 43
Hashing 251 => 51
Hashing 252 => 202
Hashing 253 => 2
Hashing 254 => 51
Hashing 255 => 53

那里仍然有一个粗略的模式,但请注意,它只不过是我们的底层函数(已经很弱)的模式。

但是请注意03即使它们不在单次运行中,也会发生碰撞。这是我之前所说的应用(碰撞阻力对于所有输入的集合保持相同,但由于基础算法中的缺陷可能会打开特定的碰撞路线)。

TL/DR 部分

通过将输入反馈到每次迭代中,我们有效地打破了之前迭代中可能发生的任何冲突。

因此,md5($input . md5($input));应该(至少在理论上md5($input))与.

这重要吗?

是的。这是 PBKDF2 在RFC 2898中替换 PBKDF1 的原因之一。考虑两者的内部循环::

PBKDF1:

T_1 = Hash (P || S) ,
T_2 = Hash (T_1) ,
...
T_c = Hash (T_{c-1}) 

c迭代计数在哪里,P密码在哪里,S盐在哪里

PBKDF2:

U_1 = PRF (P, S || INT (i)) ,
U_2 = PRF (P, U_1) ,
...
U_c = PRF (P, U_{c-1})

PRF 实际上只是一个 HMAC。但是为了我们的目的,我们只是说PRF(P, S) = Hash(P || S)(也就是说,2 个输入的 PRF 大致相同,就像两个连接在一起的哈希一样)。它不是,但就我们的目的而言,它是。

所以 PBKDF2 保持了底层Hash函数的抗碰撞性,而 PBKDF1 没有。

将所有这些捆绑在一起:

我们知道迭代哈希的安全方法。实际上:

$hash = $input;
$i = 10000;
do {
   $hash = hash($input . $hash);
} while ($i-- > 0);

通常是安全的。

现在,要了解我们为什么要散列它,让我们分析熵运动。

哈希接受无限集合:S(∞)并产生一个更小、大小一致的集合S(n)。下一次迭代(假设输入被传回)再次映射S(∞)S(n)

S(∞) -> S(n)
S(∞) -> S(n)
S(∞) -> S(n)
S(∞) -> S(n)
S(∞) -> S(n)
S(∞) -> S(n)

请注意,最终输出的熵与第一个输出完全相同。迭代不会“使它更加模糊”。熵是相同的。没有不可预测性的神奇来源(它是伪随机函数,而不是随机函数)。

然而,迭代是有好处的。它人为地使散列过程变慢。这就是为什么迭代可能是一个好主意。事实上,这是大多数现代密码散列算法的基本原理(反复做某事会使其变慢的事实)。

慢是好的,因为它正在对抗主要的安全威胁:暴力破解。我们的哈希算法越慢,攻击者就越难攻击从我们那里窃取的密码哈希。这是一件好事!

于 2013-07-01T01:26:37.237 回答
53

是的,重新散列减少了搜索空间,但不,没关系 - 有效的减少是微不足道的。

重新散列增加了蛮力所需的时间,但只这样做两次也是次优的。

您真正想要的是使用PBKDF2对密码进行哈希处理——这是一种使用带有盐和迭代的安全哈希的经过验证的方法。看看这个 SO 响应

编辑:我差点忘了——不要使用 MD5 !!!使用现代加密哈希,例如 SHA-2 系列(SHA-256、SHA-384 和 SHA-512)。

于 2008-12-07T23:49:15.017 回答
10

是的 - 它减少了与字符串匹配的可能字符串的数量。

正如您已经提到的,加盐哈希要好得多。

这里有一篇文章:http ://websecurity.ro/blog/2007/11/02/md5md5-vs-md5/ ,试图证明它为什么是等价的,但我不确定逻辑。他们部分假设没有可用于分析 md5(md5(text)) 的软件,但显然生成彩虹表相当简单。

我仍然坚持我的答案,即 md5(md5(text)) 类型的散列比 md5(text) 散列的数量少,从而增加了冲突的机会(即使仍然不太可能发生)并减少了搜索空间。

于 2008-12-07T21:33:12.917 回答
5

大多数答案是由没有密码学或安全背景的人提供的。他们错了。使用盐,如果可能的话,每条记录都是唯一的。MD5/SHA/etc 太快了,与你想要的相反。PBKDF2 和 bcrypt 速度较慢(这很好),但可以用 ASIC/FPGA/GPU 击败(现在非常实惠)。所以需要一个内存硬算法:输入 scrypt

这是关于盐和速度的外行解释(但不是关于内存硬算法)。

于 2012-08-31T13:38:44.237 回答
4

我只是从实际的角度来看这个。黑客的目的是什么?为什么,通过散列函数生成所需散列的字符组合。

您只保存最后一个哈希,因此,黑客只需暴力破解一个哈希。假设您在每个蛮力步骤中遇到所需散列的几率大致相同,则散列的数量无关紧要。您可以进行一百万次哈希迭代,它不会增加或降低安全性,因为在行尾仍然只有一个哈希要破坏,并且破坏它的几率与任何哈希相同。

也许之前的发帖人认为输入是相关的;它不是。只要您放入散列函数中的任何内容都会生成所需的散列,它就会让您通过,正确的输入或不正确的输入。

现在,彩虹桌是另一回事。由于彩虹表只携带原始密码,因此两次散列可能是一种很好的安全措施,因为包含每个散列的每个散列的彩虹表会太大。

当然,我只考虑 OP 给出的例子,它只是一个被散列的纯文本密码。如果您在哈希中包含用户名或盐,那就是另一回事了;两次散列是完全没有必要的,因为彩虹表已经太大而不能实用并且包含正确的散列。

无论如何,这里不是安全专家,但这正是我从我的经验中得出的结论。

于 2011-09-05T07:31:30.647 回答
3

通常,它不会为双重哈希或双重加密提供额外的安全性。如果您可以破坏一次哈希,则可以再次破坏它。不过,这样做通常不会损害安全性。

在您使用 MD5 的示例中,您可能知道存在一些冲突问题。“双重哈希”并不能真正帮助防止这种情况,因为相同的冲突仍然会导致相同的第一个哈希,然后您可以再次 MD5 以获得第二个哈希。

这确实可以防止字典攻击,例如那些“反向 MD5 数据库”,但盐渍也是如此。

切线,双重加密不会提供任何额外的安全性,因为它所做的只是产生一个不同的密钥,它是实际使用的两个密钥的组合。所以寻找“钥匙”的努力并没有加倍,因为实际上不需要找到两个钥匙。这不适用于散列,因为散列的结果通常与原始输入的长度不同。

于 2008-12-07T21:41:42.590 回答
3

根据我的阅读,实际上可能建议重新散列密码数百或数千次。

这个想法是,如果您可以花费更多时间来对密码进行编码,那么对于攻击者来说,通过多次猜测来破解密码的工作量会更大。这似乎是重新散列的优势——并不是说它在密码学上更安全,而是生成字典攻击只需要更长的时间。

当然,计算机一直在变得更快,因此这种优势会随着时间的推移而减弱(或需要您增加迭代次数)。

于 2008-12-07T21:46:19.553 回答
2

就我个人而言,我不会为多个散列而烦恼,但我会确保同时散列用户名(或另一个用户 ID 字段)以及密码,这样两个具有相同密码的用户就不会以相同的散列结束。此外,我可能还会将一些其他常量字符串也放入输入字符串中以进行良好的测量。

$hashed_password = md5( "xxx" + "|" + user_name + "|" + plaintext_password);
于 2008-12-07T21:54:01.877 回答
2

让我们假设您使用散列算法:计算 rot13,取前 10 个字符。如果您这样做两次(甚至 2000 次),则可以创建一个更快的函数,但给出相同的结果(即只取前 10 个字符)。

同样,可以制作一个更快的函数,它提供与重复散列函数相同的输出。因此,您选择的散列函数非常重要:与 rot13 示例一样,重复散列不会提高安全性。如果没有研究表明该算法是为递归使用而设计的,那么假设它不会为您提供额外的保护会更安全。

也就是说:对于除了最简单的散列函数之外的所有散列函数,它很可能需要密码学专家来计算更快的函数,因此,如果您要防范无法访问密码学专家的攻击者,那么在实践中使用重复散列函数可能更安全.

于 2011-06-04T13:41:16.313 回答
2

仅当我在客户端对密码进行哈希处理,然后将该哈希的哈希值(使用不同的盐)保存在服务器上时,双重哈希对我才有意义。

这样,即使有人侵入了服务器(从而忽略了 SSL 提供的安全性),他仍然无法获得清晰的密码。

是的,他将拥有入侵系统所需的数据,但他无法使用这些数据来破坏用户拥有的外部帐户。众所周知,人们几乎对任何事情都使用相同的密码。

他可以得到清晰密码的唯一方法是在客户端上安装一个注册机——这不再是你的问题了。

简而言之:

  1. 客户端上的第一个散列可以在“服务器违规”情况下保护您的用户。
  2. 如果有人持有您的数据库备份,则服务器上的第二个哈希用于保护您的系统,因此他无法使用这些密码连接到您的服务。
于 2012-09-04T11:50:01.290 回答
0

减少搜索空间的担忧在数学上是正确的,尽管搜索空间仍然足够大,对于所有实际目的(假设您使用盐),为 2^128。然而,由于我们谈论的是密码,根据我粗略的计算,可能的 16 个字符的字符串(字母数字、大写字母、一些符号)的数量大约是 2^98。因此,搜索空间的感知减少并不真正相关。

除此之外,从密码学上讲,确实没有区别。

尽管有一种称为“哈希链”的加密原语——一种允许你做一些很酷的技巧的技术,比如在使用后公开签名密钥,而不牺牲系统的完整性——给定最小的时间同步,这允许您干净地回避初始密钥分配的问题。基本上,您预先计算大量哈希值 - h(h(h(h....(h(k))...))) ,使用第 n 个值进行签名,在设定的时间间隔后,您发送取出密钥,并使用密钥 (n-1) 对其进行签名。收件人现在可以验证您是否发送了所有以前的消息,并且没有人可以伪造您的签名,因为它的有效时间已经过去。

像比尔建议的那样重新散列数十万次只是浪费你的 CPU。如果你担心人们会破坏 128 位,请使用更长的密钥。

于 2008-12-07T21:56:21.903 回答
0

正如本文中的一些回复所暗示的那样,在某些情况下它可能会提高安全性,而在其他情况下它肯定会伤害它。有一个更好的解决方案肯定会提高安全性。不是将计算哈希的次数加倍,而是将盐的大小加倍,或者将哈希中使用的位数加倍,或者两者都做!代替 SHA-245,跳到 SHA-512。

于 2008-12-08T01:06:09.697 回答
-1

我要冒昧地说在某些情况下它更安全……不过,请不要对我投反对票!

从数学/密码学的角度来看,它不太安全,因为我相信其他人会给你比我更清楚的解释。

但是,存在 MD5 哈希的大型数据库,这些数据库比它的 MD5 更有可能包含“密码”文本。因此,通过双重哈希,您正在降低这些数据库的有效性。

当然,如果你使用盐,那么这个优势(劣势?)就会消失。

于 2008-12-07T21:38:49.910 回答
-1

双散列很丑陋,因为攻击者很可能已经建立了一个表来提供大多数散列。更好的办法是给你的哈希加盐,然后把哈希混合在一起。还有新的模式来“签名”哈希(基本上是盐渍),但以更安全的方式。

于 2008-12-08T00:54:39.767 回答
-1

是的。

绝对不要使用传统哈希函数的多次迭代,例如md5(md5(md5(password))). 最好的情况是安全性略有提高(这样的方案几乎不能提供任何针对 GPU 攻击的保护;只需将其流水线化即可。)在最坏的情况下,每次添加的迭代都会减少哈希空间(从而减少安全性). 在安全方面,明智的做法是假设最坏的情况。

请务必使用由称职的密码学家设计的密码,该密码是有效的密码哈希,并且可以抵抗蛮力和时空攻击。这些包括 bcrypt、scrypt,在某些情况下还包括 PBKDF2。glibc 基于 SHA-256 的散列也是可以接受的。

于 2012-08-31T18:23:53.577 回答