ここでの問題は基本的にエントロピーの問題です。それでは、そこから見てみましょう。
文字ごとのエントロピー
バイトあたりのエントロピーのビット数は次のとおりです。
- 16 進文字
- ビット: 4
- 値: 16
- 72 文字のエントロピー: 288 ビット
- 英数字
- ビット: 6
- 値: 62
- 72 文字のエントロピー: 432 ビット
- 「共通」記号
- ビット: 6.5
- 値: 94
- 72 文字のエントロピー: 468 ビット
- フルバイト
- ビット: 8
- 値: 255
- 72 文字のエントロピー: 576 ビット
したがって、私たちがどのように行動するかは、期待するキャラクターのタイプによって異なります。
最初の問題
コードの最初の問題は、「ペッパー」ハッシュ ステップが 16 進文字を出力していることです (4 番目のパラメーターhash_hmac()
が設定されていないため)。
したがって、コショウをハッシュすることで、パスワードに使用できる最大エントロピーを 2 分の 1 (576 ビットから 288 ビットまで) に効率的に削減できます。
第二の問題
ただし、そもそもエントロピーのビットsha256
しか提供しません。256
つまり、可能な 576 ビットを効果的に 256 ビットに削減しています。あなたのハッシュステップは*すぐに*、まさに定義上
、パスワードで可能なエントロピーの少なくとも50%を失います.
に切り替えることSHA512
で、これを部分的に解決できます。使用可能なエントロピーは約 12% しか減少しません。しかし、それはまだ重要な違いではありません。その 12% により、順列の数が 1/1 に減少し1.8e19
ます。それは大きな数字です...そして、それがそれを減らす要因です...
根本的な問題
根本的な問題は、72 文字を超える 3 種類のパスワードがあることです。このスタイル システムが彼らに与える影響は大きく異なります。
SHA512
注:ここから先は、生の出力(16進数ではない)を使用するペッパーシステムと比較していると仮定しています。
高エントロピーのランダム パスワード
これらは、パスワード用に大量のキーを生成するパスワードジェネレーターを使用しているユーザーです。それらはランダム (生成されたものであり、人間が選択したものではありません) であり、1 文字あたりのエントロピーが高くなります。これらのタイプは、上位バイト (文字 > 127) と一部の制御文字を使用しています。
このグループの場合、ハッシュ関数により、利用可能なエントロピーが に大幅にbcrypt
削減されます。
もう一度言わせてください。高エントロピーで長いパスワードを使用しているユーザーの場合、このソリューションはパスワードの強度を測定可能な量だけ大幅に低下させます。(72 文字のパスワードでは 62 ビットのエントロピーが失われ、より長いパスワードではさらに多くのエントロピーが失われます)
中程度のエントロピーのランダム パスワード
このグループは、一般的な記号を含むパスワードを使用していますが、上位バイトや制御文字は使用していません。これらは入力可能なパスワードです。
このグループでは、より多くのエントロピーをわずかにロック解除します (作成するのではなく、より多くのエントロピーが bcrypt パスワードに収まるようにします)。私がわずかに言うとき、私はわずかに意味します。SHA512 が持つ 512 ビットを最大限に活用すると、損益分岐点が発生します。したがって、ピークは 78 文字です。
もう一度言わせてください。このクラスのパスワードでは、エントロピーを使い果たす前に追加で 6 文字しか保存できません。
低エントロピーのランダムでないパスワード
これは、おそらくランダムに生成されていない英数字を使用しているグループです。聖書の引用などのようなもの。これらのフレーズには、1 文字あたり約 2.3 ビットのエントロピーがあります。
このグループの場合、ハッシュ化することで、より多くのエントロピーを大幅にロック解除できます (エントロピーを作成するのではなく、bcrypt パスワード入力にさらに多くを収めることができます)。損益分岐点は、エントロピーを使い果たす前に約 223 文字です。
もう一度言いましょう。このクラスのパスワードの場合、事前ハッシュによって確実にセキュリティが大幅に向上します。
バック・トゥ・ザ・リアル・ワールド
この種のエントロピー計算は、現実の世界ではあまり重要ではありません。重要なのは、エントロピーを推測することです。それが、攻撃者ができることに直接影響を与えるものです。それがあなたが最大化したいことです。
エントロピーの推測に関する研究はほとんどありませんが、指摘したい点がいくつかあります。
連続して 72 文字の正しい文字をランダムに推測できる可能性は非常に低いです。パワーボールの宝くじで 21 回当選する可能性は、この衝突が発生する可能性よりも高くなります... これが、私たちが話している数字の大きさです。
しかし、統計的にはつまずかないかもしれません。フレーズの場合、最初の 72 文字が同じである可能性は、ランダムなパスワードの場合よりもはるかに高くなります。しかし、それでもかなり低いです (1 文字あたり 2.3 ビットに基づくと、パワーボールの宝くじに 5 回当選する可能性が高くなります)。
特に
実際には、それは本当に問題ではありません。誰かが最初の 72 文字を正しく推測し、後者が大きな違いを生む可能性は非常に低いため、心配する必要はありません。なんで?
さて、あなたがフレーズを取っているとしましょう。その人が最初の 72 文字を正しく理解できた場合、その人は本当に幸運である (可能性は低い) か、それが一般的なフレーズであるかのどちらかです。それが一般的なフレーズである場合、唯一の変数はそれを作成する長さです.
例を見てみましょう。聖書から引用してみましょう (聖書が長いテキストの一般的なソースであるという理由だけで、他の理由ではありません)。
隣人の家をむさぼってはならない。あなたの隣人の妻、または彼のしもべまたは女中、彼の牛またはろば、またはあなたの隣人のものを欲しがってはならない.
180文字です。73 番目の文字はg
2 番目のneighbor's
です。そこまで推測できた場合は、 で停止するのでnei
はなく、残りの節に進んでいる可能性があります (これがパスワードの使用方法である可能性が高いためです)。したがって、「ハッシュ」はあまり追加されませんでした。
ところで: 私は絶対に聖書の引用を使用することを主張していません. 実際、正反対です。
結論
最初にハッシュすることによって長いパスワードを使用する人々を実際に助けるつもりはありません。あなたが間違いなく助けることができるいくつかのグループ。あなたが間違いなく傷つけることができるものもあります。
しかし、結局のところ、どれも過度に重要ではありません。私たちが扱っている数字は、あまりにも高すぎます。エントロピーの差はそれほど大きくありません。
bcrypt はそのままにしておく方がよいでしょう。防止しようとしている攻撃が発生する可能性よりも、ハッシングを台無しにする可能性が高くなります (文字通り、すでにそれを行っており、その間違いを犯したのはあなたが最初でも最後でもありません)。
サイトの残りの部分を保護することに集中してください。また、登録時にパスワードボックスにパスワードエントロピーメーターを追加して、パスワードの強度を示します(パスワードが長すぎてユーザーが変更したい場合があることを示します)...
それは私の少なくとも0.02ドルです(またはおそらく0.02ドル以上)...
「秘密の」コショウを使用する限り:
文字通り、1 つのハッシュ関数を bcrypt にフィードする研究はありません。したがって、「ペッパー」ハッシュを bcrypt にフィードすることで未知の脆弱性が発生するかどうかは、せいぜい不明です (そうhash1(hash2($value))
することで、衝突耐性とプリイメージ攻撃に関する重大な脆弱性が明らかになる可能性があることがわかっています)。
秘密鍵 (「コショウ」) を保存することを既に検討していることを考えると、十分に研究され理解されている方法で秘密鍵を使用してみませんか? ハッシュを保存する前に暗号化しないのはなぜですか?
基本的に、パスワードをハッシュした後、ハッシュ出力全体を強力な暗号化アルゴリズムにフィードします。次に、暗号化された結果を保存します。
現在、SQL インジェクション攻撃は、暗号鍵を持っていないため、有用なものを漏らすことはありません。また、キーが漏洩した場合、攻撃者は、プレーン ハッシュを使用した場合よりも有利になることはありません (これは証明可能であり、ペッパーの「事前ハッシュ」を使用したものでは提供されません)。
注: これを行う場合は、ライブラリを使用してください。PHP については、Zend Framework 2 のパッケージを強くお勧めします。Zend\Crypt
実際、現時点で私がお勧めするのはこれだけです。十分に検討されており、ユーザーに代わってすべての決定を下します (これは非常に良いことです)...
何かのようなもの:
use Zend\Crypt\BlockCipher;
public function createHash($password) {
$hash = password_hash($password, PASSWORD_BCRYPT, ["cost"=>$this->cost]);
$blockCipher = BlockCipher::factory('mcrypt', array('algo' => 'aes'));
$blockCipher->setKey($this->key);
return $blockCipher->encrypt($hash);
}
public function verifyHash($password, $hash) {
$blockCipher = BlockCipher::factory('mcrypt', array('algo' => 'aes'));
$blockCipher->setKey($this->key);
$hash = $blockCipher->decrypt($hash);
return password_verify($password, $hash);
}
そして、(少なくとも比較的) 十分に理解され、十分に研究された方法ですべてのアルゴリズムを使用しているため、有益です。覚えて:
最も無知なアマチュアから最高の暗号学者まで、誰でも、自分では解読できないアルゴリズムを作成できます。