web-dev-qa-db-ja.com

「ダブルハッシュ」は、1回ハッシュするよりも安全性が低いですか?

保管する前にパスワードを2回ハッシュすることは、1回ハッシュするだけで多少安全ですか?

私が話しているのはこれを行うことです:

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

これだけではなく:

$hashed_password = hash($plaintext_password);

安全性が低い場合、適切な説明(またはリンク)を提供できますか?

また、使用されるハッシュ関数は違いを生みますか?同じハッシュ関数を繰り返す代わりに、たとえばmd5とsha1を混ぜると違いはありますか?

注1:「ダブルハッシュ」と言うとき、パスワードを2回ハッシュすることで、パスワードをわかりにくくしようとしています。 衝突を解決するためのテクニック については話していない。

注2:本当に安全にするためにランダムなソルトを追加する必要があることは知っています。問題は、同じアルゴリズムで2回ハッシュするのがハッシュを助けるか傷つけるかです。

285
Bill the Lizard

一度パスワードをハッシュすることは安全ではありません

いいえ、複数のハッシュはそれほど安全ではありません。それらは安全なパスワード使用の重要な部分です。

ハッシュを繰り返すと、攻撃者が候補リスト内の各パスワードを試行するのにかかる時間が長くなります。パスワードを攻撃するのにかかる時間を数時間から数年に簡単に増やすことができます。

単純な反復では不十分です

ハッシュ出力を入力にチェーンするだけでは、セキュリティには不十分です。反復は、パスワードのエントロピーを保持するアルゴリズムのコンテキストで実行する必要があります。幸いなことに、設計に自信を持たせるのに十分な精査を持った公開されたアルゴリズムがいくつかあります。

PBKDF2のような優れたキー派生アルゴリズムは、ハッシュの各ラウンドにパスワードを注入し、ハッシュ出力の衝突に関する懸念を軽減します。 PBKDF2は、そのままパスワード認証に使用できます。 Bcryptは、キーの派生に続いて暗号化手順を実行します。そのようにして、キーの派生を逆にする高速な方法が発見された場合、攻撃者は依然として既知の平文攻撃を完了する必要があります。

パスワードを破る方法

保存されたパスワードには、オフライン攻撃からの保護が必要です。パスワードがソルトされていない場合、事前に計算された辞書攻撃(たとえば、Rainbow Tableを使用)でパスワードが破られる可能性があります。それ以外の場合、攻撃者は各パスワードのハッシュを計算し、保存されたハッシュと一致するかどうかを確認するために時間を費やす必要があります。

すべてのパスワードが等しくなるとは限りません。攻撃者はすべての短いパスワードを徹底的に検索する可能性がありますが、キャラクターが追加されるたびに総当たり攻撃の成功の可能性が急激に低下することを知っています。代わりに、最も可能性の高いパスワードの順序付きリストを使用します。これらは「password123」で始まり、使用頻度の低いパスワードに進みます。

攻撃者リストが長く、100億の候補者がいるとします。また、デスクトップシステムが1秒あたり100万のハッシュを計算できるとします。攻撃者は、反復が1回だけ使用されている場合、リスト全体が3時間未満であることをテストできます。しかし、2000回の反復だけを使用すると、その時間はほぼ8か月になります。より洗練された攻撃者(たとえば、GPUの能力を活用できるプログラムをダウンロードできる攻撃者)を倒すには、さらに反復が必要です。

いくらですか?

使用する反復回数は、セキュリティとユーザーエクスペリエンスのトレードオフです。攻撃者が使用できる特殊なハードウェアは安価ですが、 1秒あたり数億回の反復を実行できます攻撃者のパフォーマンスシステムは、反復回数を指定してパスワードを破るのにかかる時間を決定します。しかし、アプリケーションはこの特殊なハードウェアを使用する可能性は低いです。ユーザーを悪化させることなく実行できる反復回数は、yourシステムに依存します。

おそらく、認証中にユーザーにさらに3/4秒ほど待たせることができます。ターゲットプラットフォームのプロファイルを作成し、できるだけ多くの反復を使用します。テストしたプラットフォーム(モバイルデバイス上の1人のユーザー、またはサーバープラットフォーム上の多くのユーザー)は、 PBKDF2 60,000から120,000の反復で、または bcrypt でコストを快適にサポートできます12または13の係数。

より多くの背景

ハッシュのソルトと反復の役割に関する信頼できる情報については、PKCS#5をお読みください。 PBKDF2はパスワードから暗号化キーを生成するためのものでしたが、パスワード認証のための一方向ハッシュとしてはうまく機能します。 bcryptの各反復はSHA-2ハッシュよりも高価であるため、使用する反復の数を減らすことができますが、考え方は同じです。また、Bcryptは、派生キーを使用して既知のプレーンテキストを暗号化することにより、ほとんどのPBKDF2ベースのソリューションを超えています。結果の暗号テキストは、メタデータとともに「ハッシュ」として保存されます。ただし、PBKDF2で同じことを行うことを妨げるものは何もありません。

このトピックについて私が書いた他の回答は次のとおりです。

255
erickson

安全だと言う人には、彼らは正しい一般的にです。 「ダブル」ハッシュ(またはその論理的な拡張、ハッシュ関数の反復)は、特定の懸念事項については、絶対に安全です正しければ

安全でないと言う人には、彼らは正しいこの場合です。質問is insecureに投稿されたコード。理由について話しましょう:

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

私たちが心配しているハッシュ関数の2つの基本的なプロパティがあります:

  1. Pre-Image Resistance-ハッシュ$hを指定すると、$h === hash($m)のようなメッセージ$mを見つけるのは難しいはずです

  2. Second-Pre-Image Resistance-$m1というメッセージが与えられた場合、hash($m1) === hash($m2)などの別のメッセージ$m2を見つけるのは難しいはずです

  3. Collision Resistance-hash($m1) === hash($m2)(これはSecond-Pre-Imageレジスタンスに似ていることに注意してください($m1, $m2)のメッセージのペアを見つけるのは難しいはずですが、ここでは、攻撃者が両方のメッセージを制御できるという点で異なります)...

パスワードの保存について、私たちが本当に気にするのはPre-Image Resistanceです。 $m1は安全を保とうとしているユーザーのパスワードであるため、他の2つは意味がありません。攻撃者がすでに持っている場合、ハッシュには保護するものがありません...

免責事項

以下はすべて、Pre-Image Resistanceだけが重要であるという前提に基づいています。ハッシュ関数の他の2つの基本的なプロパティは、同じ方法で保持されない場合があります(通常は保持されません)。したがって、この投稿の結論はパスワードの保存にハッシュ関数を使用する場合にのみ適用可能です。一般には適用されません...

始めましょう

この議論のために、独自のハッシュ関数を発明しましょう。

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個の値に絞り込みました... 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

反復回数が多いほど、削減はさらに進みます。

修正

幸いなことに、これを修正するtrivialの方法があります:フィードバックsomethingをさらに繰り返します:

$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の一般的なケースは、1ラウンドの場合と同じくらい強力です。

待ってた? 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

そこにはまだ大まかなパターンがありますが、基になる関数(既にかなり弱かった)よりもパターンのmoreではないことに注意してください。

ただし、03は、シングルランではなかったにもかかわらず、衝突になったことに注意してください。これは私が以前に言ったことの適用です(衝突抵抗はすべての入力のセットで同じままですが、特定の衝突ルートは基礎となるアルゴリズムの欠陥のために開く可能性があります)。

TL/DRセクション

入力を各反復にフィードバックすることにより、前の反復で発生した可能性のある衝突を効果的に解消します。

したがって、md5($input . md5($input));は(少なくとも理論的にはmd5($input)と同じくらい強い必要があります。

これは重要ですか?

はい。これは、PBKDF2が RFC 2898 でPBKDF1を置き換えた理由の1つです。 2つの内部ループを考えてみましょう。

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は、大まかに言えば、2つを連結したハッシュと同じです)とだけ言ってみましょう。 notですが、私たちの目的ではそうです。

そのため、PBKDF1はそうではないが、PBKDF2は基礎となるHash関数の衝突抵抗を維持します。

すべてを結びつける:

ハッシュを反復する安全な方法を知っています。実際には:

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

通常は安全です。

さて、whyに入るために、それをハッシュしたいので、エントロピーの動きを分析しましょう。

ハッシュは、無限のセットS(∞)を取り、より小さく、一貫したサイズのセットS(n)を生成します。次の反復(入力が戻されると仮定)は、S(∞)S(n)に再びマッピングします。

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

最終出力には最初のエントロピーとまったく同じ量のエントロピーがあることに注意してください。反復するとnot "より不明瞭になります"。エントロピーは同じです。予測不可能な魔法のソースはありません(ランダム関数ではなく、擬似ランダム関数です)。

ただし、反復処理には利点があります。ハッシュ処理が人為的に遅くなります。そして、それが反復が良いアイデアである理由です。実際、これはほとんどの最新のパスワードハッシュアルゴリズムの基本原則です(何度も繰り返し実行すると遅くなるという事実です)。

スローは、主なセキュリティの脅威であるブルートフォーシングと戦うため、優れています。ハッシュアルゴリズムを遅くすればするほど、攻撃者は私たちから盗まれたパスワードハッシュを攻撃するために努力しなければなりません。そしてそれは良いことです!!!

221
ircmaxell

はい、再ハッシュは検索スペースを削減しますが、重要ではありません-効果的な削減は重要ではありません。

再ハッシュは、ブルートフォースにかかる時間を増加させますが、2回だけ実行することも最適ではありません。

本当に必要なのは、パスワードを PBKDF2 でハッシュすることです。これは、ソルトと反復でセキュアハッシュを使用する実証済みの方法です。 this SO response を確認してください。

編集: 忘れそうだった - MD5は使用しないでください!!!! SHA-2ファミリー(SHA-256、SHA-384、SHA-512)などの最新の暗号化ハッシュを使用します。

49
orip

はい-文字列に一致する可能性のある文字列の数を減らします。

既に述べたように、塩漬けハッシュははるかに優れています。

こちらの記事: http://websecurity.ro/blog/2007/11/02/md5md5-vs-md5/ 、それが同等である理由を証明しようとしますが、ロジック。一部には、md5(md5(text))を分析するためのソフトウェアがないと想定していますが、Rainbowテーブルを作成するのは明らかに簡単です。

私はまだmd5(md5(text))タイプのハッシュの数がmd5(text)ハッシュよりも少ないため、衝突の可能性を(まだありそうにない確率であっても)増やし、検索スペースを減らすという答えに固執しています。

10
Rich Bradshaw

私はこれを実際的な見地から見ています。ハッカーは何の後ですか?なぜ、ハッシュ関数を通過したときに、希望のハッシュを生成する文字の組み合わせ。

保存するのは最後のハッシュだけなので、ハッカーは1つのハッシュをブルートフォースするだけで済みます。各ブルートフォースステップで目的のハッシュをほぼ同じ確率でつまずくと仮定すると、ハッシュの数は関係ありません。百万回のハッシュ反復を行うことができますが、行の終わりにはまだ1つだけのハッシュがあり、それを壊す可能性は他のハッシュと同じであるため、セキュリティが1ビット増加または減少することはありません。

たぶん、以前のポスターは、入力が関連していると考えています。そうではありません。ハッシュ関数に入力したものが目的のハッシュを生成する限り、正しい入力または誤った入力を通過できます。

さて、レインボーテーブルは別の話です。 Rainbowテーブルは生のパスワードのみを保持するため、すべてのハッシュのすべてのハッシュを含むRainbowテーブルは大きすぎるため、2回のハッシュは適切なセキュリティ対策になる可能性があります。

もちろん、私はOPが与えた例だけを考えています。それはハッシュされている単なるテキストパスワードです。ハッシュにユーザー名またはソルトを含めると、別の話になります。 Rainbowテーブルは実際には大きすぎて適切なハッシュが含まれていないため、2回のハッシュはまったく不要です。

とにかく、ここのセキュリティの専門家ではありませんが、それはまさに私の経験から考えたものです。

4
Pat

ほとんどの回答は、暗号化やセキュリティの背景のない人々によるものです。そして、彼らは間違っています。可能であれば、レコードごとに固有のソルトを使用します。 MD5/SHA/etcは速すぎます、あなたが望むものの反対。 PBKDF2とbcryptは低速ですが(良好ですが)、ASIC/FPGA/GPUで無効にすることができます(今日では非常に手頃な価格です)。そのため、メモリハードアルゴリズムが必要です: scryptを入力

ここに 素人の説明 saltと速度があります(ただし、メモリハードアルゴリズムについてではありません)。

4
alecco

私が読んだことから、実際にはパスワードを何百または何千回も再ハッシュすることが推奨されるかもしれません。

アイデアは、パスワードをエンコードするのにより多くの時間をかけることができる場合、攻撃者がパスワードを解読するために多くの推測を実行するのがより多くの仕事であるということです。これは、再ハッシュすることの利点のようです-暗号的に安全であるということではなく、辞書攻撃を生成するのに時間がかかります。

もちろん、コンピューターは常に高速になるので、この利点は時間とともに減少します(または反復を増やす必要があります)。

3
Bill Karwin

個人的には複数のハッシュを気にしませんが、serName(または別のユーザーIDフィールド)とパスワードもハッシュするを確認して、同じパスワードを持つ2人のユーザーが同じハッシュになります。また、適切な測定のために、おそらく他の定数文字列も入力文字列にスローします。

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

ハッシュアルゴリズムを使用すると仮定します。rot13を計算し、最初の10文字を取得します。これを2回(または2000回も)行うと、より高速な関数を作成できますが、同じ結果が得られます(最初の10文字を取得するだけです)。

同様に、繰り返されるハッシュ関数と同じ出力を提供するより高速な関数を作成することも可能です。したがって、ハッシュ関数の選択は非常に重要です。rot13の例のように、ハッシュを繰り返してセキュリティを向上させることはできません。アルゴリズムが再帰的な使用のために設計されているという研究がない場合は、追加の保護が提供されないと想定する方が安全です。

つまり、最も単純なハッシュ関数を除くすべての場合、暗号化の専門家がより高速な関数を計算する必要がある可能性が高いため、暗号化の専門家にアクセスできない攻撃者から保護する場合、実際には繰り返しハッシュ関数を使用する方が安全です。

2
Ole Tange

この記事のいくつかの回答が示唆しているように、セキュリティを改善する場合と、明らかにそれを傷つける場合があります。セキュリティを確実に改善するより良いソリューションがあります。ハッシュを計算する回数を2倍にする代わりに、ソルトのサイズを2倍にするか、ハッシュで使用されるビット数を2倍にするか、またはその両方を行います。 SHA-245の代わりに、SHA-512にジャンプします。

1
Stefan Rusek

一般に、ハッシュを二重化したり、何かを二重暗号化する追加のセキュリティは提供されません。ハッシュを1回破ることができる場合は、もう一度破ることができます。ただし、通常、これを行うことでセキュリティが損なわれることはありません。

MD5の使用例では、おそらく衝突の問題があることをご存知でしょう。 「ダブルハッシュ」は、これに対する保護に実際には役立ちません。同じ衝突でも同じ最初のハッシュが得られるため、MD5を再度使用して2番目のハッシュを取得できます。

これにより、「逆MD5データベース」のような辞書攻撃から保護されますが、ソルティングも保護されます。

接線では、二重暗号化は追加のセキュリティを提供しません。それは、実際に使用される2つのキーの組み合わせである異なるキーが得られるためです。したがって、実際には2つのキーを見つける必要がないため、「キー」を見つけるための労力は2倍になりません。ハッシュの結果は通常、元の入力と同じ長さではないため、これはハッシュには当てはまりません。

1
SoapBox

ダブルハッシュは、クライアントでパスワードをハッシュし、そのハッシュのハッシュを(異なるソルトで)サーバーに保存する場合にのみ意味があります。

そうすれば、誰かがサーバーに侵入したとしても(SSLが提供する安全性を無視して)、クリアなパスワードを取得することはできません。

はい、彼はシステムに侵入するために必要なデータを持っていますが、ユーザーが持っている外部アカウントを侵害するためにそのデータを使用することはできません。そして、人々は事実上すべてに同じパスワードを使用することが知られています。

彼がクリアなパスワードを取得できる唯一の方法は、クライアントにkeygenをインストールすることです-それはもうあなたの問題ではありません。

要するに:

  1. クライアントでの最初のハッシュは、「サーバー侵害」シナリオでユーザーを保護します。
  2. サーバーの2番目のハッシュは、誰かがデータベースのバックアップを保持した場合にシステムを保護するのに役立つため、それらのパスワードを使用してサービスに接続することはできません。
1
Vedran

サーチスペースの削減に関する懸念は数学的には正しいですが、サーチスペースは2 ^ 128ですべての実用的な目的(ソルトを使用する場合)に十分な大きさのままです。ただし、パスワードについて説明しているため、可能性のある16文字の文字列(英数字、大文字、いくつかの記号が挿入される)の数は、エンベロープの計算によると約2 ^ 98です。したがって、検索スペースの知覚される減少は、実際には関係ありません。

それ以外は、暗号的に言えば、実際には違いはありません。

「ハッシュチェーン」と呼ばれる暗号プリミティブがあります-システムの整合性を犠牲にすることなく、使用後に署名キーを開示するなど、いくつかのクールなトリックを行うことができる技術-最小限の時間同期を与え、初期キー配布の問題をきれいに回避できます。基本的に、ハッシュのハッシュの大きなセットを事前に計算します-h(h(h(h....(h(k))...)))、n番目の値を使用して、後に署名します設定された間隔で、キーを送信し、キー(n-1)を使用して署名します。受信者は以前のメッセージをすべて送信したことを確認できるようになり、有効期間が過ぎてからだれも署名を偽造できなくなります。

Billが示唆しているように、数十万回の再ハッシュはCPUの無駄に過ぎません。128ビットを壊す人が心配な場合は、より長いキーを使用してください。

0
SquareCog