Is “double hashing” a password less secure than just hashing it once?

后端 未结 16 1767
梦谈多话
梦谈多话 2020-11-22 08:09

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         


        
相关标签:
16条回答
  • 2020-11-22 08:53

    I just look at this from a practical standpoint. What is the hacker after? Why, the combination of characters that, when put through the hash function, generates the desired hash.

    You are only saving the last hash, therefore, the hacker only has to bruteforce one hash. Assuming you have roughly the same odds of stumbling across the desired hash with each bruteforce step, the number of hashes is irrelevant. You could do a million hash iterations, and it would not increase or reduce security one bit, since at the end of the line there's still only one hash to break, and the odds of breaking it are the same as any hash.

    Maybe the previous posters think that the input is relevant; it's not. As long as whatever you put into the hash function generates the desired hash, it will get you through, correct input or incorrect input.

    Now, rainbow tables are another story. Since a rainbow table only carries raw passwords, hashing twice may be a good security measure, since a rainbow table that contains every hash of every hash would be too large.

    Of course, I'm only considering the example the OP gave, where it's just a plain-text password being hashed. If you include the username or a salt in the hash, it's a different story; hashing twice is entirely unnecessary, since the rainbow table would already be too large to be practical and contain the right hash.

    Anyway, not a security expert here, but that's just what I've figured from my experience.

    0 讨论(0)
  • 2020-11-22 08:53

    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.

    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.

    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.
    0 讨论(0)
  • 2020-11-22 08:54

    As several responses in this article suggest, there are some cases where it may improves security and others where it definately hurts it. There is a better solution that will definately improve security. Instead of doubling the number of times you calculate the hash, double the size of your salt, or double the number of bits used int the hash, or do both! Instead of SHA-245, jump up to SHA-512.

    0 讨论(0)
  • 2020-11-22 08:56

    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)

    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)

    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)...

    For the storage of passwords, all we really care about is Pre-Image Resistance. The other two would be moot, because $m1 is the user's password we're trying to keep safe. 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.

    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:

    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). That is we've created a Surjective Function mapping $input to $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).

    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. It maps S(∞) onto S(2^128). Since there's no guarantee that running MD5(S(output)) will be Injective, meaning that it won't have collisions.

    TL/DR Section

    Therefore, since feeding the output back to md5 directly can generate collisions, every iteration will increase the chance of collisions. 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.

    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. 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). But the general case for $input is still as strong as it was for a single round.

    Wait, was it? Let's test this out with our ourHash() function. Switching to $hash = ourHash($input . $hash);, for 100 iterations:

    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. 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

    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)); should be (theoretically at least) as strong as md5($input).

    Is This Important?

    Yes. This is one of the reasons that PBKDF2 replaced PBKDF1 in RFC 2898. Consider the inner loops of the two::

    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

    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. 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). 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.

    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). The next iteration (assuming the input is passed back in) maps S(∞) onto S(n) again:

    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!!!

    0 讨论(0)
  • 2020-11-22 08:57

    Hashing a password once is insecure

    No, multiple hashes are not less secure; they are an essential part of secure password use.

    Iterating the hash increases the time it takes for an attacker to try each password in their list of candidates. You can easily increase the time it takes to attack a password from hours to years.

    Simple iteration is not enough

    Merely chaining hash output to input isn't sufficient for security. The iteration should take place in the context of an algorithm that preserves the entropy of the password. Luckily, there are several published algorithms that have had enough scrutiny to give confidence in their design.

    A good key derivation algorithm like PBKDF2 injects the password into each round of hashing, mitigating concerns about collisions in hash output. PBKDF2 can be used for password authentication as-is. Bcrypt follows the key derivation with an encryption step; that way, if a fast way to reverse the key derivation is discovered, an attacker still has to complete a known-plaintext attack.

    How to break a password

    Stored passwords need protection from an offline attack. If passwords aren't salted, they can be broken with a pre-computed dictionary attack (for example, using a Rainbow Table). Otherwise, the attacker must spend time to compute a hash for each password and see if it matches the stored hash.

    All passwords are not equally likely. Attackers might exhaustively search all short passwords, but they know that their chances for brute-force success drop sharply with each additional character. Instead, they use an ordered list of the most likely passwords. They start with "password123" and progress to less frequently used passwords.

    Let's say an attackers list is long, with 10 billion candidates; suppose also that a desktop system can compute 1 million hashes per second. The attacker can test her whole list is less than three hours if only one iteration is used. But if just 2000 iterations are used, that time extends to almost 8 months. To defeat a more sophisticated attacker—one capable of downloading a program that can tap the power of their GPU, for example—you need more iterations.

    How much is enough?

    The number of iterations to use is a trade-off between security and user experience. Specialized hardware that can be used by attackers is cheap, but it can still perform hundreds of millions of iterations per second. The performance of the attacker's system determines how long it takes to break a password given a number of iterations. But your application is not likely to use this specialized hardware. How many iterations you can perform without aggravating users depends on your system.

    You can probably let users wait an extra ¾ second or so during authentication. Profile your target platform, and use as many iterations as you can afford. Platforms I've tested (one user on a mobile device, or many users on a server platform) can comfortably support PBKDF2 with between 60,000 and 120,000 iterations, or bcrypt with cost factor of 12 or 13.

    More background

    Read PKCS #5 for authoritative information on the role of salt and iterations in hashing. Even though PBKDF2 was meant for generating encryption keys from passwords, it works well as a one-way-hash for password authentication. Each iteration of bcrypt is more expensive than a SHA-2 hash, so you can use fewer iterations, but the idea is the same. Bcrypt also goes a step beyond most PBKDF2-based solutions by using the derived key to encrypt a well-known plain text. The resulting cipher text is stored as the "hash," along with some meta-data. However, nothing stops you from doing the same thing with PBKDF2.

    Here are other answers I've written on this topic:

    • Hashing passwords
    • Hashing passwords
    • Salt
    • Hiding salt
    • PBKDF2 versus bcrypt
    • Bcrypt
    0 讨论(0)
  • 2020-11-22 08:59

    From what I've read, it may actually be recommended to re-hash the password hundreds or thousands of times.

    The idea is that if you can make it take more time to encode the password, it's more work for an attacker to run through many guesses to crack the password. That seems to be the advantage to re-hashing -- not that it's more cryptographically secure, but it simply takes longer to generate a dictionary attack.

    Of course computers get faster all the time, so this advantage diminishes over time (or requires you to increase the iterations).

    0 讨论(0)
提交回复
热议问题