“双重哈希”密码是否比仅仅哈希一次更安全?

本文翻译自:Is “double hashing” a password less secure than just hashing it once?

Is hashing a password twice before storage any more or less secure than just hashing it once? 在存储之前对密码进行两次哈希处理的安全性是否比仅仅哈希一次更安全?

What I'm talking about is doing this: 我在说什么是这样做的:

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

instead of just this: 而不仅仅是这个:

$hashed_password = hash($plaintext_password);

If it is less secure, can you provide a good explanation (or a link to one)? 如果它不太安全,你能提供一个很好的解释(或链接到一个)吗?

Also, does the hash function used make a difference? 此外,使用的哈希函数是否有所作为? Does it make any difference if you mix md5 and sha1 (for example) instead of repeating the same hash function? 如果混合使用md5和sha1(例如)而不是重复相同的散列函数,它会有什么不同吗?

Note 1: When I say "double hashing" I'm talking about hashing a password twice in an attempt to make it more obscured. 注1:当我说“双重哈希”时,我正在谈论两次哈希密码以试图使其更加模糊。 I'm not talking about the technique for resolving collisions . 我不是在谈论解决碰撞技术

Note 2: I know I need to add a random salt to really make it secure. 注2:我知道我需要添加一个随机盐来真正使其安全。 The question is whether hashing twice with the same algorithm helps or hurts the hash. 问题是使用相同算法进行两次散列是否有助于或损害散列。


#1楼

参考:https://stackoom.com/question/1syF/双重哈希-密码是否比仅仅哈希一次更安全


#2楼

Most answers are by people without a background in cryptography or security. 大多数答案都是没有加密或安全背景的人。 And they are wrong. 他们错了。 Use a salt, if possible unique per record. 如果可能,每个记录使用盐。 MD5/SHA/etc are too fast, the opposite of what you want. MD5 / SHA /等太快了,与你想要的相反。 PBKDF2 and bcrypt are slower (wich is good) but can be defeated with ASICs/FPGA/GPUs (very afordable nowadays). PBKDF2和bcrypt较慢(这是好的)但可以用ASIC / FPGA / GPU(现在非常合适)来击败。 So a memory-hard algorithm is needed: enter scrypt . 因此需要一个内存难的算法: 输入scrypt

Here's a layman explanation on salts and speed (but not about memory-hard algorithms). 这是关于盐和速度的外行解释 (但不是关于记忆硬算法)。


#3楼

Yes. 是。

Absolutely do not use multiple iterations of a conventional hash function, like md5(md5(md5(password))) . 绝对不要使用传统哈希函数的多次迭代,如md5(md5(md5(password))) At best you will be getting a marginal increase in security (a scheme like this offers hardly any protection against a GPU attack; just pipeline it.) At worst, you're reducing your hash space (and thus security) with every iteration you add. 最好的情况是你的安全性会略有提高(像这样的方案几乎没有提供任何针对GPU攻击的保护;只需管道它。)最糟糕的是,你在每次添加的迭代中都减少了哈希空间(从而降低了安全性) 。 In security, it's wise to assume the worst. 在安全方面,假设最坏的情况是明智的。

Do use a password has that's been designed by a competent cryptographer to be an effective password hash, and resistant to both brute-force and time-space attacks. 一定要使用一个密码一个已经设计由主管密码破译是一个有效的密码哈希,并都蛮力和时空抵抗攻击。 These include bcrypt, scrypt, and in some situations PBKDF2. 这些包括bcrypt,scrypt,以及在某些情况下PBKDF2。 The glibc SHA-256-based hash is also acceptable. 基于glibc SHA-256的哈希也是可以接受的。


#4楼

Double hashing makes sense to me only if I hash the password on the client, and then save the hash (with different salt) of that hash on the server. 只有当我在客户端上散列密码,然后在服务器上保存该散列的散列(使用不同的盐)时,双散列才对我有意义。

That way even if someone hacked his way into the server (thereby ignoring the safety SSL provides), he still can't get to the clear passwords. 这样即使有人入侵服务器(从而忽略了SSL提供的安全性),他仍然无法获得明确的密码。

Yes he will have the data required to breach into the system, but he wouldn't be able to use that data to compromise outside accounts the user has. 是的,他将拥有违反系统所需的数据,但他无法使用该数据来破坏用户拥有的外部帐户。 And people are known to use the same password for virtually anything. 众所周知,人们几乎可以使用相同的密码。

The only way he could get to the clear passwords is installing a keygen on the client - and that's not your problem anymore. 他可以获得明确密码的唯一方法是在客户端安装keygen - 这不再是你的问题了。

So in short: 简而言之:

  1. The first hashing on the client protects your users in a 'server breach' scenario. 客户端上的第一次散列可以在“服务器违规”情况下保护您的用户。
  2. The second hashing on the server serves to protect your system if someone got a hold of your database backup, so he can't use those passwords to connect to your services. 如果有人抓住您的数据库备份,服务器上的第二次散列用于保护您的系统,因此他无法使用这些密码连接到您的服务。

#5楼

To those who say it's secure, they are correct in general . 对于那些说它是安全的人来说,他们一般都是正确 "Double" hashing (or the logical expansion of that, iterating a hash function) is absolutely secure if done right , for a specific concern. 对于特定问题, 如果做得好 ,“双重”散列(或其逻辑扩展,迭代散列函数)绝对安全。

To those who say it's insecure, they are correct in this case . 对于那些说它不安全的人来说, 在这种情况下他们是正确的。 The code that is posted in the question is insecure. 问题中发布的代码不安全的。 Let's talk about why: 我们来谈谈原因:

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

There are two fundamental properties of a hash function that we're concerned about: 我们关注的哈希函数有两个基本属性:

  1. Pre-Image Resistance - Given a hash $h , it should be difficult to find a message $m such that $h === hash($m) 预映像电阻 - 给定哈希值$h ,应该很难找到消息$m这样$h === hash($m)

  2. Second-Pre-Image Resistance - Given a message $m1 , it should be difficult to find a different message $m2 such that hash($m1) === hash($m2) 第二预映像电阻 - 给定消息$m1 ,应该很难找到不同的消息$m2 ,使得hash($m1) === hash($m2)

  3. Collision Resistance - It should be difficult to find a pair of messages ($m1, $m2) such that hash($m1) === hash($m2) (note that this is similar to Second-Pre-Image resistance, but different in that here the attacker has control over both messages)... 碰撞阻力 - 应该很难找到一对消息($m1, $m2)这样hash($m1) === hash($m2) (注意这类似于Second-Pre-Image阻力,但是不同之处在于攻击者可以控制这两个消息)...

For the storage of passwords , all we really care about is Pre-Image Resistance . 对于密码的存储 ,我们真正关心的是Pre-Image Resistance The other two would be moot, because $m1 is the user's password we're trying to keep safe. 另外两个是没有实际意义的,因为$m1是我们试图保持安全的用户密码。 So if the attacker already has it, the hash has nothing to protect... 因此,如果攻击者已经拥有它,那么哈希就无法保护...

DISCLAIMER 免责声明

Everything that follows is based on the premise that all we care about is Pre-Image Resistance . 接下来的一切都基于这样一个前提,即我们所关心的只是前映像抗性 The other two fundamental properties of hash functions may not (and typically don't) hold up in the same way. 散列函数的另外两个基本属性可能不会(并且通常不会)以相同的方式保持。 So the conclusions in this post are only applicable when using hash functions for the storage of passwords. 因此,本文中的结论仅适用于使用哈希函数存储密码。 They are not applicable in general... 它们一般不适用......

Let's Get Started 让我们开始吧

For the sake of this discussion, let's invent our own hash function: 为了便于讨论,让我们发明一下我们自己的哈希函数:

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

Now it should be pretty obvious what this hash function does. 现在,这个哈希函数的功能应该非常明显。 It sums together the ASCII values of each character of input, and then takes the modulo of that result with 256. 它将输入的每个字符的ASCII值相加,然后将该结果的模数取为256。

So let's test it out: 所以让我们测试一下:

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

Now, let's see what happens if we run it a few times around a function: 现在,让我们看看如果我们在函数周围运行几次会发生什么:

$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";
}

That outputs: 那输出:

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

Hrm, wow. 嗯,哇。 We've generated collisions!!! 我们已经产生了碰撞! Let's try to look at why: 让我们试着看看为什么:

Here's the output of hashing a string of each and every possible hash output: 这是散列每个可能的散列输出的字符串的输出:

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

Notice the tendency towards higher numbers. 注意到更高数字的趋势。 That turns out to be our deadfall. 结果证明是我们的死亡。 Running the hash 4 times ($hash = ourHash($hash)`, for each element) winds up giving us: 运行哈希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

We've narrowed ourselves down to 8 values... That's bad ... Our original function mapped S(∞) onto S(256) . 我们将自己缩小到8个值......这很糟糕 ......我们的原始函数将S(∞)映射到S(256) That is we've created a Surjective Function mapping $input to $output . 那就是我们创建了一个Surjective函数映射$input$output

Since we have a Surjective function, we have no guarantee the mapping for any subset of the input won't have collisions (in fact, in practice they will). 由于我们有一个Surjective函数,我们无法保证输入的任何子集的映射都不会发生冲突(事实上,实际上它们会发生冲突)。

That's what happened here! 这就是这里发生的事情! Our function was bad, but that's not why this worked (that's why it worked so quickly and so completely). 我们的功能很糟糕,但这不是为什么这有效(这就是为什么它如此快速和如此完整)。

The same thing happens with MD5 . MD5 It maps S(∞) onto S(2^128) . 它将S(∞)映射到S(2^128) Since there's no guarantee that running MD5(S(output)) will be Injective , meaning that it won't have collisions. 由于无法保证运行MD5(S(output))将是Injective ,这意味着它不会发生冲突。

TL/DR Section TL / DR部分

Therefore, since feeding the output back to md5 directly can generate collisions, every iteration will increase the chance of collisions. 因此,由于直接将输出反馈到md5会产生碰撞,每次迭代都会增加碰撞的机会。 This is a linear increase however, which means that while the result set of 2^128 is reduced, it's not significantly reduced fast enough to be a critical flaw. 然而,这是线性增加,这意味着虽然减少了2^128的结果集,但它没有显着降低到足以成为关键缺陷。

So, 所以,

$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

The more times you iterate, the further the reduction goes. 迭代的次数越多,减少的次数就越多。

The Fix 修复

Fortunately for us, there's a trivial way to fix this: Feed back something into the further iterations: 对我们来说幸运的是,有一种简单的方法可以解决这个问题:在进一步的迭代中反馈一些东西

$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    

Note that the further iterations aren't 2^128 for each individual value for $input . 请注意, $input每个单独值的进一步迭代不是2 ^ 128。 Meaning that we may be able to generate $input values that still collide down the line (and hence will settle or resonate at far less than 2^128 possible outputs). 这意味着我们可能能够生成仍然在线下冲突的$input值(因此将在远小于2^128可能的输出处稳定或共振)。 But the general case for $input is still as strong as it was for a single round. $input的一般情况仍然与单轮投资一样强劲。

Wait, was it? 等等,是吗? Let's test this out with our ourHash() function. 让我们用ourHash()函数测试一下。 Switching to $hash = ourHash($input . $hash); 切换到$hash = ourHash($input . $hash); , for 100 iterations: ,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

There's still a rough pattern there, but note that it's no more of a pattern than our underlying function (which was already quite weak). 仍然有粗糙的花纹有,但要注意,它没有比我们的基本功能(这是已经相当弱) 更多的模式的。

Notice however that 0 and 3 became collisions, even though they weren't in the single run. 但请注意, 03成为碰撞,即使它们不在单次运行中。 That's an application of what I said before (that the collision resistance stays the same for the set of all inputs, but specific collision routes may open up due to flaws in the underlying algorithm). 这是我之前所说的应用(碰撞阻力对于所有输入的集合保持相同,但是由于底层算法中的缺陷,特定的碰撞路径可能会打开)。

TL/DR Section TL / DR部分

By feeding back the input into each iteration, we effectively break any collisions that may have occurred in the prior iteration. 通过将输入反馈到每次迭代中,我们有效地破坏了先前迭代中可能发生的任何冲突。

Therefore, md5($input . md5($input)); 因此, md5($input . md5($input)); should be ( theoretically at least) as strong as md5($input) . 应该( 理论上至少)与md5($input)一样强。

Is This Important? 这很重要吗?

Yes. 是。 This is one of the reasons that PBKDF2 replaced PBKDF1 in RFC 2898 . 这是PBKDF2取代RFC 2898中的 PBKDF1的原因之一。 Consider the inner loops of the two:: 考虑两个内部循环::

PBKDF1: PBKDF1:

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

Where c is the iteration count, P is the Password and S is the salt 其中c是迭代计数, P是密码, S是盐

PBKDF2: PBKDF2:

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

Where PRF is really just a HMAC. PRF真的只是一个HMAC。 But for our purposes here, let's just say that PRF(P, S) = Hash(P || S) (that is, the PRF of 2 inputs is the same, roughly speaking, as hash with the two concatenated together). 但是对于我们这里的目的,我们只是说PRF(P, S) = Hash(P || S) (也就是说,2个输入的PRF是相同的,粗略地说,就像两个连接在一起的哈希)。 It's very much not , but for our purposes it is. 不是 ,但就我们的目的而言。

So PBKDF2 maintains the collision resistance of the underlying Hash function, where PBKDF1 does not. 因此,PBKDF2维持底层Hash函数的碰撞阻力,而PBKDF1则没有。

Tie-ing All Of It Together: 将所有这些结合在一起:

We know of secure ways of iterating a hash. 我们知道迭代哈希的安全方法。 In fact: 事实上:

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

Is typically safe. 通常是安全的。

Now, to go into why we would want to hash it, let's analyze the entropy movement. 现在,为了解释为什么我们想要哈希它,让我们分析熵运动。

A hash takes in the infinite set: S(∞) and produces a smaller, consistently sized set S(n) . 哈希采用无限集: S(∞)并产生一个较小的,一致大小的集合S(n) The next iteration (assuming the input is passed back in) maps S(∞) onto S(n) again: 下一次迭代(假设输入被传回)将S(∞) S(n)再次映射到S(n)

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

Notice that the final output has exactly the same amount of entropy as the first one . 请注意,最终输出与第一个输出具有完全相同的熵量 Iterating will not "make it more obscured". 迭代不会 “让它变得更加模糊”。 The entropy is identical. 熵是相同的。 There's no magic source of unpredictability (it's a Pseudo-Random-Function, not a Random Function). 没有不可预测性的神奇来源(它是伪随机函数,而不是随机函数)。

There is however a gain to iterating. 然而,迭代有一个好处。 It makes the hashing process artificially slower. 它使散列过程人为地变慢。 And that's why iterating can be a good idea. 这就是为什么迭代可能是一个好主意。 In fact, it's the basic principle of most modern password hashing algorithms (the fact that doing something over-and-over makes it slower). 事实上,它是大多数现代密码散列算法的基本原则(事实上,做一些反复使得它变慢)。

Slow is good, because it's combating the primary security threat: brute forcing. 慢是好的,因为它正在对抗主要的安全威胁:暴力强迫。 The slower we make our hashing algorithm, the harder attackers have to work to attack password hashes stolen from us. 我们制作哈希算法的速度越慢,攻击者就越难以攻击我们窃取的密码哈希值。 And that's a good thing!!! 那是件好事!!!


#6楼

Yes - it reduces the number of possibly strings that match the string. 是 - 它减少了与字符串匹配的可能字符串的数量。

As you have already mentioned, salted hashes are much better. 正如你已经提到的,盐渍哈希要好得多。

An article here: http://websecurity.ro/blog/2007/11/02/md5md5-vs-md5/ , attempts a proof at why it is equivalent, but I'm not sure with the logic. 这里的一篇文章: http//websecurity.ro/blog/2007/11/02/md5md5-vs-md5/ ,尝试证明它为何等同,但我不确定逻辑。 Partly they assume that there isn't software available to analyse md5(md5(text)), but obviously it's fairly trivial to produce the rainbow tables. 部分他们假设没有可用于分析md5(md5(文本))的软件,但显然生成彩虹表是相当简单的。

I'm still sticking with my answer that there are smaller number of md5(md5(text)) type hashes than md5(text) hashes, increasing the chance of collision (even if still to an unlikely probability) and reducing the search space. 我仍然坚持我的答案,md5(md5(文本))类型的哈希值比md5(文本)哈希值少,增加了碰撞的几率(即使仍然不太可能)并减少了搜索空间。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值