首页 文章

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

提问于
浏览
278

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

我在说什么是这样做的:

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

而不仅仅是这个:

$hashed_password = hash($plaintext_password);

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

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

注1:当我说"double hashing"我'm talking about hashing a password twice in an attempt to make it more obscured. I'我不是在谈论technique for resolving collisions .

Note 2: I know I need to add a random salt to really make it secure. The question is whether hashing twice with the same algorithm helps or hurts the hash.

16 回答

  • 248

    哈希密码一次是不安全的

    不,多个哈希不是那么安全;它们是安全密码使用的重要组成部分 .

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

    简单的迭代是不够的

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

    一个好的密钥派生算法,如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之间,或者成本系数为12或13的bcrypt .

    更多背景

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

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

  • 46

    对于那些说它安全的人来说,他们是正确的 in general . "Double"哈希(或其逻辑扩展,迭代哈希函数)绝对安全 if done right ,针对特定问题 .

    对于那些说它不安全的人来说,他们是正确的 in this case . 发布在 is 问题中的代码不安全 . 我们来谈谈原因:

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

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

    • 前映像电阻 - 给定一个散列 $h ,应该很难找到一条消息 $m 这样 $h === hash($m)

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

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

    For the storage of passwords ,我们真正关心的是Pre-Image Resistance . 另外两个是没有实际意义的,因为 $m1 是用户's password we'试图保持安全 . 因此,如果攻击者已经拥有它,那么哈希就无法保护...

    免责声明

    接下来的一切都基于这样一个前提,即我们关心的是Pre-Image Resistance . 散列函数的另外两个基本属性可能不会(并且通常不会)以相同的方式保持 . 所以这篇文章的结论是 only applicable when using hash functions for the storage of passwords. They are not applicable in general...

    让我们开始吧

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

    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
    

    我们've narrowed ourselves down to 8 values... That' s bad ...我们的原始函数将 S(∞) 映射到 S(256) . 那就是我们创建了一个Surjective Function映射 $input$output .

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

    这就是这里发生的事情!我们的功能很糟糕,但这不是为什么这样做的原因(这就是为什么它如此快速而如此完整) .

    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是相同的,粗略地说,就像两个连接在一起的哈希) . 这非常 not ,但就我们的目的而言 .

    因此,PBKDF2保持底层 Hash 函数的碰撞阻力,而PBKDF1则没有 .

    将所有这些结合在一起:

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

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

    通常是安全的 .

    现在,进入 why 我们想要哈希它,让我们分析熵运动 .

    哈希采用无限集: S(∞) 并生成一个更小,一致大小的集 S(n) . 下一次迭代(假设输入被传回in)再次将 S(∞) 映射到 S(n)

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

    请注意,最终输出具有 exactly the same amount of entropy as the first one . 迭代将 not "make it more obscured" . 熵是相同的 . 那里有一个伪随机函数,而不是一个随机函数 .

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

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

  • 0

    是的,重新散列减少了搜索空间,但不是,无关紧要 - 有效减少是微不足道的 .

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

    你真正想要的是用PBKDF2散列密码 - 一种使用盐和迭代的安全散列的成熟方法 . 看看this SO response .

    EDIT :我差点忘了 - DON'T USE MD5!!!! 使用现代加密哈希,例如SHA-2系列(SHA-256,SHA-384和SHA-512) .

  • 0

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

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

    这里有一篇文章:http://websecurity.ro/blog/2007/11/02/md5md5-vs-md5/,尝试证明它为什么是等价的,但是我可以用来分析md5(md5(文本)),但显然生成彩虹表是相当简单的 .

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

  • 2

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

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

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

    比尔建议的数十万次重新散列只是浪费你的cpu ..如果你担心人们打破128位,请使用更长的密钥 .

  • 0

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

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

    也许以前的海报认为输入是相关的;不是 . 只要您放入哈希函数中的任何内容都会生成所需的哈希值,它就会让您通过,输入正确或输入错误 .

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

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

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

  • 1

    就个人而言,我确保't bother with multiple hashses, but I'确保 also hash the UserName (or another User ID field) as well as the password 所以两个拥有相同密码的用户赢得't end up with the same hash. Also I' d可能会将一些其他常量字符串输入到输入字符串中以获得良好的衡量标准 .

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

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

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

  • 3

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

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

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

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

  • 2

    从我所读到的,它实际上可能是建议重新散列密码数百或数千次 .

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

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

  • 2

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

  • 3

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

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

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

  • 2

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

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

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

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

    简而言之:

    • 客户端上的第一个散列在'server breach'场景中保护您的用户 .

    • 服务器上的第二次散列用于保护您的系统,如果有人 grab 您的数据库备份,那么他就无法使用这些密码连接到您的服务 .

  • 210

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

  • 5

    是的 .

    绝对 do not 使用传统散列函数的多次迭代,如 md5(md5(md5(password))) . 最好的情况是你的安全性会略有提高(像这样的方案几乎没有提供任何针对GPU攻击的保护;只需要管道它 . )在最坏的情况下,你应该明智地承担最坏的情况 .

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

  • 2

    我打算走出去,说它在某些情况下会更安全......但是不要低估我!

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

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

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

相关问题