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

时间:2008-12-07 21:31:21

标签: security hash passwords cryptography password-hash

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

我所说的是这样做:

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

而不仅仅是这个:

$hashed_password = hash($plaintext_password);

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

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

注1:当我说“双重哈希”时,我说的是两次哈希密码,试图让它更加模糊。我不是在谈论technique for resolving collisions

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

16 个答案:

答案 0 :(得分:253)

哈希密码一次是不安全的

不,多个哈希的安全性不低;它们是安全密码使用的重要组成部分。

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

简单的迭代是不够的

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

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

如何破解密码

存储的密码需要防止脱机攻击。如果密码没有被腌制,则可以使用预先计算的字典攻击(例如,使用彩虹表)来破坏密码。否则,攻击者必须花时间为每个密码计算一个哈希值,看看它是否与存储的哈希值匹配。

所有密码都不一样。攻击者可能会详尽地搜索所有短密码,但他们知道,每增加一个角色,他们蛮力成功的机会就会急剧下降。相反,他们使用最可能的密码的有序列表。它们以“password123”开头,并进入不经常使用的密码。

让我们说攻击者名单很长,有100亿候选人;假设桌面系统每秒可以计算100万个哈希值。如果只使用一次迭代,攻击者可以测试她的整个列表不到三个小时。但如果仅使用2000次迭代,则该时间延长至近8个月。要击败更复杂的攻击者 - 例如能够下载可以利用其GPU功能的程序的攻击者 - 你需要更多的迭代。

多少钱?

要使用的迭代次数是安全性和用户体验之间的权衡。攻击者可以使用的专用硬件很便宜,但是it can still perform hundreds of millions of iterations per second. 攻击者的系统的性能决定了在多次迭代的情况下中断密码需要多长时间。但是您的应用程序不太可能使用这种专用硬件。在不加剧用户的情况下可以执行多少次迭代取决于您的系统。

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

更多背景

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

以下是我就此主题撰写的其他答案:

答案 1 :(得分:219)

对于那些说它安全的人来说,他们是正确的一般。 "双"哈希(或其逻辑扩展,迭代哈希函数)绝对安全如果做得好,针对特定问题。

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

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

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

  1. 预图像抗性 - 给定哈希$h,应该很难找到$m消息$h === hash($m)

  2. 第二次图像前阻力 - 如果收到消息$m1,则应该很难找到$m2的其他消息hash($m1) === hash($m2)

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

  4. 对于密码存储,我们真正关心的是 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)。我们已经创建了Surjective Function映射$input$output

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

    发生在这里的事情!我们的功能很糟糕,但这不是为什么这样做的原因(这就是为什么它如此迅速和如此完整地工作)。

    同样的事情发生在MD5上。它将S(∞)映射到S(2^128)。由于无法保证MD5(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    
    

    请注意,对于$input的每个单独值,进一步的迭代不是2 ^ 128。这意味着我们可以生成仍然在线下碰撞的$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)
    

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

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

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

答案 2 :(得分:48)

是的,重新散列会减少搜索空间,但不会,这无关紧要 - 有效减少是无关紧要的。

重新散列会增加蛮力所需的时间,但这样做只有两次也不是最理想的。

您真正想要的是使用PBKDF2哈希密码 - 这是一种使用盐和迭代的安全哈希的经过验证的方法。查看this SO response

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

答案 3 :(得分:10)

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

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

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

我仍然坚持我的答案,md5(md5(文本))类型的哈希值比md5(文本)哈希值少,增加了碰撞的几率(即使仍然不太可能)并减少搜索空间。

答案 4 :(得分:4)

我只是从实际的角度来看待这个问题。之后的黑客是什么?为什么,字符组合在通过哈希函数时会生成所需的哈希值。

您只保存最后一个哈希值,因此,黑客只需要强制使用一个哈希值。假设你在每个暴力步骤中遇到所需散列的几率相同,那么散列的数量是无关紧要的。你可以进行一百万次哈希迭代,并且它不会增加或减少一点安全性,因为在该行的末尾仍然只有一个哈希要破解,并且破坏它的几率与任何哈希相同。

也许以前的海报认为输入是相关的;不是。只要您放入哈希函数中的任何内容生成所需的哈希值,它都会引导您完成输入或输入错误。

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

当然,我只考虑OP提供的示例,其中只是一个纯文本密码被哈希。如果你在哈希中包含用户名或盐,这是一个不同的故事;散列两次是完全没必要的,因为彩虹表已经太大而不实用并且包含正确的散列。

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

答案 5 :(得分:4)

大多数答案都是没有加密或安全背景的人。他们错了。如果可能,每个记录使用盐。 MD5 / SHA /等太快了,与你想要的相反。 PBKDF2和bcrypt速度较慢(这是好的),但可以用ASIC / FPGA / GPU(现在非常合理)来打败。因此需要一个内存难的算法:enter scrypt

这是关于盐和速度的layman explanation(但不是关于内存硬算法)。

答案 6 :(得分:3)

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

这个想法是,如果你可以花费更多的时间对密码进行编码,那么攻击者通过许多猜测来破解密码的工作就更多了。这似乎是重新散列的优势 - 而不是它更加加密,但是生成字典攻击只需要更长的时间。

当然计算机总是变得更快,所以这种优势会随着时间的推移而减少(或者需要你增加迭代次数)。

答案 7 :(得分:2)

就个人而言,我不打扰多个散列,但我确保也散列UserName(或其他用户ID字段)以及密码,这样两个用户使用相同的密码不会以相同的哈希结束。此外,我可能也会在输入字符串中输入一些其他常量字符串,以便进行测量。

$hashed_password = md5( "xxx" + "|" + user_name + "|" + plaintext_password);

答案 8 :(得分:2)

我们假设您使用散列算法:计算rot13,取前10个字符。如果你这样做了两次(甚至2000次),就可以制作一个更快的功能,但是它会得到相同的结果(即只需要前10个字符)。

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

这就是说:除了最简单的哈希函数之外,最有可能需要加密专家来计算更快的函数,所以如果你要防范那些无法访问加密专家的攻击者,那么在实践中使用重复哈希函数。

答案 9 :(得分:1)

通常,它没有提供双重哈希或双重加密的额外安全性。如果你可以打破哈希一次,你可以再次打破它。但是,这通常不会损害安全性。

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

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

在切线上,Double加密某些内容不提供任何额外的安全性,因为它所做的只是导致一个不同的键,它是实际使用的两个键的组合。因此,找到“密钥”的努力不会加倍,因为实际上不需要找到两个密钥。对于散列,情况并非如此,因为散列的结果通常与原始输入的长度不同。

答案 10 :(得分:1)

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

答案 11 :(得分:1)

只有在客户端上散列密码,然后在服务器上保存该散列的散列(使用不同的盐)时,双散列才对我有意义。

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

是的,他将拥有违反系统所需的数据,但他无法使用该数据来破坏用户的外部帐户。众所周知,人们几乎可以使用相同的密码。

他可以获得明确密码的唯一方法是在客户端安装keygen - 这不再是你的问题了。

简而言之:

  1. 客户端上的第一次散列可以在“服务器违规”情况下保护您的用户。
  2. 如果有人抓住您的数据库备份,服务器上的第二次散列用于保护您的系统,因此他无法使用这些密码连接到您的服务。

答案 12 :(得分:0)

关于减少搜索空间的问题在数学上是正确的,尽管搜索空间足够大以至于所有实际目的(假设你使用盐),在2 ^ 128。但是,由于我们正在谈论密码,根据我的背包计算,可能的16个字符的字符串(字母数字,上限,投入的几个符号)的数量大约为2 ^ 98。因此,搜索空间的感知减少并不是真正相关的。

除此之外,从密码学的角度来看确实没有区别。

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

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

答案 13 :(得分:-1)

我会站出来说在某些情况下它会更安全......但是不要低估我!

从数学/加密的角度来看,它不太安全,因为我确信别人会给你一个比我更清楚的解释。

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

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

答案 14 :(得分:-1)

双重哈希是丑陋的,因为攻击者很可能已经建立了一个表来提供大多数哈希值。更好的是为你的哈希加盐,并将哈希混合在一起。还有新的模式来“签署”哈希(基本上是盐腌),但是以更安全的方式。

答案 15 :(得分:-1)

绝对使用传统哈希函数的多次迭代,例如md5(md5(md5(password)))。在 best ,你的安全性会略有提高(像这样的方案几乎没有提供任何针对GPU攻击的保护;只需管道它。)最坏的情况是,你减少了哈希空间(因此安全性)与您添加的每次迭代。在安全方面,假设最坏的情况是明智的。

使用密码已被设计的密码作为有效的密码哈希,并且能够抵御暴力攻击和时空攻击。这些包括bcrypt,scrypt,以及在某些情况下PBKDF2。基于glibc SHA-256的哈希也是可以接受的。