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


293

与仅对一次哈希进行哈希处理相比,对密码进行两次哈希存储之前或多或少的安全性高吗?

我在说的是这样做:

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

不仅仅是这样:

$hashed_password = hash($plaintext_password);

如果它的安全性较差,您能提供一个很好的解释(或一个链接)吗?

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

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

注意2:我知道我需要添加随机盐以确保其安全。问题是,使用相同算法进行两次哈希处理是否有助于或损害哈希。


2
Hash(password)Hash(Hash(password))同样不安全。两者都缺乏语义安全的概念。即,输出与随机区分开。例如,MD5("password")5f4dcc3b5aa765d61d8327deb882cf99。我知道这是的MD5哈希值password可以与随机区分开。相反,您应该使用HMAC。它可证明的安全性和PRF。
jww 2014年

Answers:


267

一次哈希密码不安全

不,多个散列的安全性同样不错。它们是使用安全密码的重要组成部分。

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

简单的迭代是不够的

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

诸如PBKDF2之类的良好密钥派生算法会将密码注入到每一轮哈希中,从而减轻了对哈希输出冲突的担忧。PBKDF2可以原样用于密码验证。Bcrypt在密钥派生之后执行加密步骤;这样,如果发现了一种逆转密钥派生的快速方法,则攻击者仍然必须完成已知明文攻击。

如何破解密码

存储的密码需要免受脱机攻击的保护。如果不加密码,则可以通过预先计算的字典攻击(例如,使用Rainbow Table)来破坏密码。否则,攻击者必须花时间为每个密码计算哈希值,并查看它是否与存储的哈希值匹配。

所有密码的可能性均不相同。攻击者可能会穷举搜索所有短密码,但他们知道,每增加一个字符,暴力破解成功的机会就会大大降低。相反,他们使用最有可能的密码的有序列表。它们以“ password123”开头,然后发展为使用频率较低的密码。

假设攻击者名单很长,有100亿个候选人。还假设桌面系统每秒可以计算一百万个哈希。如果只使用一次迭代,攻击者可以测试少于3个小时的整个列表。但是,如果仅使用2000次迭代,则时间将延长到将近8个月。为了击败更高级的攻击者(例如,能够下载可以利用其GPU功能的程序的攻击者),您需要进行多次迭代。

多少钱够了?

要使用的迭代次数是安全性和用户体验之间的折衷方案。攻击者可以使用的专用硬件价格便宜,但每秒仍可以执行数亿次迭代。攻击者系统的性能决定了给定多次迭代后破解密码所花费的时间。但是您的应用程序不太可能使用这种专用硬件。在不增加用户负担的情况下可以执行多少次迭代取决于您的系统。

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

更多背景

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

这是我针对该主题写的其他答案:


68
当您尝试防止针对已损坏的身份验证存储库的字典攻击时,有意地制定慢速算法是一种公认​​的做法。该技术称为“键加强”或“键拉伸”。参见en.wikipedia.org/wiki/Key_stretching

17
@RoBorg:您的实现速度有多慢都没有关系,但是攻击者的实现有多慢:如果哈希本身慢了数千倍,那么强行破解密码就需要攻击者数千倍的时间。
orip

5
可以说,您希望在128位空间0到2 ^ 128-1之间发生冲突。如果哈希算法的2 ^ 128输出空间是完美的,那么从理论上讲,您只需要具有2 ^ 128个字形的字母的替换密码。
jmucchiello

13
@devin-这不是“我的解决方案”,而是一种广泛接受的做法,已内置在基于密码的加密标准中,例如PKCS#5,并得到了Robert Morris等专家的推荐。它具有极强的可扩展性,因为在合法应用程序中,用于验证用户的时间所占的比例很小。只有在您的应用程序破解密码时才很难扩展,因此建议您这样做。当然,哈希的搜索空间小于可能的密码,但是即使是128位的空间也太大了,无法进行强力搜索。防御的威胁是离线字典攻击。
erickson

6
我指的不是给单个用户带来不便,而是指如果您的用户群很大,这将给服务器带来压力,因为您依赖于CPU负载来减慢请求数量。这意味着,如果您添加更多的CPU功能,则可以减少对那些蛮力攻击者的限制。-但是,您对可伸缩性和被广泛接受的做法完全正确。我在先前的评论中所说的几乎所有内容都是错误的。抱歉:)
DevinB,2009年

227

对于那些说这是安全的人,他们通常是正确如果正确的话,“ Double”哈希(或逻辑扩展,迭代一个哈希函数)对于特定问题绝对是安全的。

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

$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的抵抗,但不同之处在于攻击者可以控制这两个消息)...

对于密码的存储,我们真正关心的只是图像前电阻。另外两个是没有意义的,因为$m1我们正在尝试保护用户的密码。因此,如果攻击者已经拥有该哈希,则哈希没有任何保护措施...

免责声明

接下来的一切都基于前提,即我们所关心的只是“像前电阻”。散列函数的其他两个基本属性可能不会(通常不会)以相同的方式出现。因此,本文中的结论仅适用于使用哈希函数存储密码的情况。它们通常不适用...

让我们开始吧

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

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

由于我们具有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    

请注意,对于的每个单独的值,进一步的迭代都不会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)(也就是说,两个输入的PRF大致相同,因为哈希值是两个连接在一起的哈希值)。这不是很大,但就我们的目的而言。

因此,PBKDF2保持基础Hash功能的抗碰撞性,而PBKDF1则没有。

将所有内容捆绑在一起:

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

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

通常是安全的。

现在,进入为什么我们要对其进行散列的原因,让我们分析熵的运动。

散列采用无限集:S(∞)并产生较小的且大小一致的set S(n)。下一次迭代(假设输入被传递回)再次映射S(∞)S(n)

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

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

但是有迭代的好处。这使得哈希过程人为地变慢了。这就是为什么迭代可以是一个好主意的原因。实际上,这是大多数现代密码哈希算法的基本原理(事实是反复进行会使速度变慢)。

慢是好的,因为它可以抵抗主要的安全威胁:暴力破解。我们使用散列算法的速度越慢,攻击者就越需要努力攻击从我们那里窃取的密码哈希。那是一件好事!!!


1
$output = md5($output); // < 2^128 possibilities---是不是真的严格<,还是<=
zerkms

2
@zerkms:严格来说不是。我们需要知道一些基础函数的非常具体的细节(md5()在这种情况下)才能真正确定。但总的来说,它应该是…… <而不是<=……请记住,我们正在谈论的$output所有可能的集合的大小$inputs。因此,即使发生一次碰撞,也将是<,因此<是更好的泛化器。
ircmaxell

2
@TomášFejfar我认为问题不是一般的冲突,而是严格的输出集中的冲突(2 ^ 128个输出,每个正好是128位宽)。那可能是内射的,但据我所知,不可能有通用的证明(仅针对特定算法的碰撞证明为例)。考虑一下哈希函数,如果它是128位,则仅返回输入(否则返回哈希)。总的来说,这将是排斥的,但是当输出其输出时,它将始终是
排斥的


6
对于那些想节省时间而不需要去节省时间的人,请检查Dan和ircmaxell之间的讨论是如何完成的,它的结局很好:Dan同意ircmaxell。
jeromej 2015年

51

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

重新散列会增加暴力破解所需的时间,但是仅执行两次也是次优的。

您真正想要的是使用PBKDF2对密码进行哈希处理-这是一种通过盐和迭代使用安全哈希的行之有效的方法。查看此SO响应

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


2
@DFTR-同意。bcrypt或scrypt是更好的选择。
orip 2012年

不要使用它们(SHA-2系列)中的任何一个,它们现在也可以很容易地被破解,请检查crackstation.net以作为证明。如果有的话,请使用scrypt或PBKDF2,它们是基于密钥派生函数(KDF)的加密哈希函数。
西奥多2014年

3
在2016年,Argon2和scrypt是每个人都应努力使用的工具
Silkfire '16

10

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

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

此处的文章:http : //websecurity.ro/blog/2007/11/02/md5md5-vs-md5/,试图证明为什么它是等效的,但是我不确定逻辑。他们部分地假设没有可用的软件来分析md5(md5(text)),但是显然产生彩虹表是相当琐碎的。

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


5

大多数答案是由没有密码学或安全性背景的人提供的。他们错了。如果可能的话,每条记录使用唯一的盐。MD5 / SHA / etc太快了,与您想要的相反。PBKDF2和bcrypt速度较慢(非常好),但是可以用ASIC / FPGA / GPU击败(如今非常便宜)。因此,需要一种难于记忆的算法:输入scrypt

这是关于盐和速度的外行说明(但不是有关内存困难算法的说明)。


4

我只是从实际的角度来看。黑客是什么?为什么,当使用哈希函数时,字符组合会生成所需的哈希。

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

也许先前的张贴者认为输入是相关的;不是。只要您将任何放入哈希函数中的内容生成所需的哈希,它都会使您正确输入或输入错误。

现在,彩虹桌又是另一个故事了。由于Rainbow表仅包含原始密码,因此两次哈希是一种很好的安全措施,因为包含每个哈希的每个哈希的Rainbow表将太大。

当然,我仅考虑OP给出的示例,该示例只是散列的纯文本密码。如果您在哈希中包含用户名或盐,则完全不同。两次哈希是完全没有必要的,因为彩虹表已经太大而无法实用,并且包含正确的哈希。

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


这个答案在各个方面都是错误的。1.知道倒数第二个哈希值对攻击者没有任何价值,因为迭代哈希值的输入是password,然后将其哈希多次(而不是一次)。2.输入空间为密码,输出空间为哈希密码。典型密码的空间比输出空间小得多。3.用于未加盐的双哈希密码的Rainbow表不大于用于未加盐的单哈希密码的Rainbow表。4.用户名是低熵的,好的盐是随机的。5.加盐不能代替迭代。你们两个都需要。
克莱门特·切林

3

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

这样做的想法是,如果您可以花更多的时间来编码密码,那么对于攻击者而言,要经过许多猜测才能破解密码,这将需要更多的工作。这似乎是重新哈希的优点-并不是说它在密码学上更安全,而是只需花费更长的时间来产生字典攻击。

当然,计算机始终会变得越来越快,因此,这种优势会随着时间的流逝而减弱(或要求您增加迭代次数)。


我也在另一条评论中提到了这一点,但是 en.wikipedia.org/wiki/Key_stretching

2

就我个人而言,我不会打扰多个散列,但我还要确保也散列UserName(或另一个User ID字段)以及密码,这样具有相同密码的两个用户就不会以相同的散列结尾。同样,我也可能为了输入数据而将其他一些常量字符串也放入了输入字符串中。

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

13
实际上,它应该是为每个用户随机生成的字符串,而不是常量。
比尔蜥蜴

7
如果您按照建议的方式输入用户名,那么恒定的秘密将起作用(并且更易于使用)。这本质上会产生一个随机的用户特定密钥。
SquareCog

4
持续不断的秘密盐是通过隐秘获得的安全性。如果“秘密”得知您使用的是“ xxx” +用户名+密码,那么攻击者甚至不需要表中的数据来发起针对它的攻击。
比尔蜥蜴

8
我认为这不是默默无闻的安全措施。使用盐的原因是,您无法同时针对多个md5哈希计算彩虹表。一次为“ xxx” +密码(相同的盐)生成一个。为“ xxx” +用户名+密码构建表比强制破解更糟糕。
FryGuy

5
@比尔蜥蜴:“攻击被简化为建立一个字典来攻击特定的用户名”,只是蛮力攻击(实际上甚至更糟,因为除了计算所有散列之外,您还必须存储它们)在这种情况下完美。
Kornel

2

让我们假设您使用哈希算法:计算rot13,取前10个字符。如果您执行两次(甚至2000次),则可以使函数运行得更快,但结果却相同(即仅获取前10个字符)。

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

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


1

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

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

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

在切线上,对某事进行双重加密不会提供任何额外的安全性,因为它所做的全部都是导致产生一个不同的密钥,该密钥是实际使用的两个密钥的组合。因此,查找“密钥”的工作不会增加一倍,因为实际上并不需要找到两个密钥。对于散列而言,情况并非如此,因为散列的结果通常与原始输入的长度不同。


1
完全正确,但我只想指出,强抗碰撞折衷方案对MD5的影响有些过分了-使用加密哈希函数的大多数方案并不依赖于强抗碰撞性,而仅依赖于抗弱性。他们不受此漏洞影响。
SquareCog

1

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

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

是的,他将拥有入侵系统所需的数据,但他将无法使用该数据来破坏用户拥有的外部帐户。人们知道几乎所有东西都使用相同的密码。

他获取清晰密码的唯一方法是在客户端上安装Keygen-这不再是您的问题。

简而言之:

  1. 在“服务器违规”情况下,客户端上的第一个哈希可以保护用户。
  2. 如果有人拥有您的数据库备份,服务器上的第二个哈希可以保护您的系统,因此他不能使用这些密码来连接到您的服务。

1
+1我一直在等待看到这样的答案,因为我想到的是您不想在客户端上存储纯文本密码,也不想通过网络发送最终的加密密码的相同情况。与数据库的简单比较。
标记

1
对于网络应用程序无济于事。如果您的服务器受到威胁,则服务器发送给客户端的代码也会受到威胁。攻击者将禁用您的客户端哈希并捕获原始密码。
克莱门特·切林

0

尽管搜索空间仍然足够大以至于可以用于所有实际目的(假设您使用盐),但在2 ^ 128的范围内,关于减小搜索空间的担忧在数学上是正确的。但是,由于我们谈论的是密码,根据我的计算结果,可能的16个字符的字符串(字母数字,大写字母,插入的几个符号)的数量大约为2 ^ 98。因此,感知到的搜索空间的减少并不是真正相关的。

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

尽管有一个称为“哈希链”的加密原语-一种使您能够做一些很酷的技巧的技术,例如在使用签名密钥后公开它,而又不牺牲系统的完整性-在最小的时间同步下,这使您可以完全避免初始密钥分发的问题。基本上,您预先计算了大量的哈希值-h(h(h(h ....(h(k))...))),使用第n个值进行签名,在设置的时间间隔后,发送取出密钥,然后使用密钥(n-1)对其进行签名。接收者现在可以验证您是否已发送所有先前的消息,并且自从有效期过去以来,没有人可以伪造您的签名。

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


1
重新散列恰恰是要减慢散列速度。这是基于密码的加密中的关键安全功能。请参阅PCKS5和PBKDF2的链接。
orip

0

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


这不能回答问题。
比尔蜥蜴

1
双重哈希处理不值得,但是将哈希大小加倍是值得的。我认为这是更有价值的一点。
Stefan Rusek

-1

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


-1

是。

绝对不要使用常规哈希函数的多次迭代,例如md5(md5(md5(password)))。在最好的你会得到安全边际增加(这样的报价几乎没有针对GPU攻击任何保护方案,只是管道的。)在最坏的情况,你与你添加的每个迭代减少你的散列空间(并因此安全性) 。在安全方面,假设最坏的情况是明智的。

一定要使用由合格的密码专家设计的有效密码,并且可以抵抗暴力破解和时空攻击。这些包括bcrypt,scrypt,在某些情况下还包括PBKDF2。基于glibc SHA-256的哈希也是可以接受的。


-1

我要弯腰说在某些情况下它是更安全的。

从数学/密码学的角度来看,它的安全性较低,原因是我确信其他人会给您比我更清楚的解释。

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

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.