web-dev-qa-db-ja.com

パスワードハッシュのHMACSHA512とRfc2898DeriveBytes

現在HMACSHA512を.netで使用しています128Char(64byte)validation key塩はランダムに生成された64文字文字列。ハッシュ化されたbase64文字列の結果として、データベースに2048の長さを割り当てました。それは一般向けのウェブサイトになります。 このアプローチは妥当ですかまたは、Rfc2898DeriveBytesなどの別のアプローチに変更する必要がありますか?

 public string HashEncode(string password, string salt, string validationKey) {
        byte[] hashKey = BosUtilites.HexStringToByteArray(validationKey);
        var sha512 = new HMACSHA512(hashKey);
        var hashInput = BosUtilites.StringToByteArray(password + salt);
        byte[] hash = sha512.ComputeHash(hashInput);
        return Convert.ToBase64String(hash);
    }

 public string GenerateSimpleSalt(int Size = 64) {
        var alphaSet = new char[64]; // use 62 for strict alpha... that random generator for alphas only
        //nicer results with set length * int i = 256. But still produces excellent random results.
        //alphaset plus 2.  Reduce to 62 if alpha requried
        alphaSet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890#=".ToCharArray();
        var tempSB = GenerateRandomString(Size, alphaSet);
        return tempSB.ToString();
    }

    public StringBuilder GenerateRandomString(int Size, char[] alphaSet) {
        using (var crypto = new RNGCryptoServiceProvider()) {
            var bytes = new byte[Size];
            crypto.GetBytes(bytes); //get a bucket of very random bytes
            var tempSB = new StringBuilder(Size);
            foreach (var b in bytes) { // use b , a random from 0-255 as the index to our source array. Just mod on length set
                tempSB.Append(alphaSet[b%(alphaSet.Length)]);
            }

            return tempSB;
        }

編集2:誰かがグーグル経由でこれを見つけた場合、私は学んだ教訓を含めましたワークステーションでのテストの平均サンプルは約300ミリ秒でした。これは、ログオン時にあまり目立たないはずです。検証キーは必要ありません。それは安心です:-)

 SCrypt package installed via nuget. and rfc2898 PBKDF2 changed to be large number or iterations but only 20bytes output.  SAme CPU time.

新しいパスワードはデフォルトでSCRYPTでエンコードされ、

  <package id="CryptSharpOfficial" version="2.0.0.0" targetFramework="net451" />
  // save salt, hash algorithm used and resulting encoding on user record
  public string PasswordEncode(string password, byte[] salt, HashAlgorithm hashAlgorithm ) {
        switch (hashAlgorithm) {
            case HashAlgorithm.PBKDF2:
                    var deriver2898 = new Rfc2898DeriveBytes(password, salt,<Use a number around 50K>); // approx 300msecs on workstation
                    byte[] hash = deriver2898.GetBytes(20); // 
                    return Convert.ToBase64String(hash);
            case HashAlgorithm.Scrypt:
                var key = Encoding.UTF8.GetBytes(password);
                byte[] hashScrypt =  SCrypt.ComputeDerivedKey(key: key, salt: salt, 
                                    cost: 65536, // must be a power of 2 !, on PC, singlethread this is approx 1 sec
                                    blockSize: 8, 
                                    parallel: 1,
                                    maxThreads: 1, 
                                    derivedKeyLength: 128);

                    return Convert.ToBase64String(hashScrypt);
            default:
                throw new ArgumentOutOfRangeException("hashAlgorithm");
        }
    }
12
soadyp

Rfc2898DeriveBytes implements PBKDF2 :パスワード(ソルト付き)を任意の長さのバイトシーケンスに変換する関数。 PBKDF2は、パスワードハッシュ関数に必要な特性を持っているため、パスワードハッシュ(つまり、verifypasswordに十分な値を計算して保存する)によく使用されます:asaltおよび設定可能な遅延

パスワードは弱いため、これらの特性が必要です:それらは人間の脳に適合します。そのため、それらは徹底的な検索に対して脆弱です。一般的に、人間のユーザーが思い付き、覚えておくほとんどのパスワードを列挙することは可能です。この攻撃は、攻撃者がソルトとハッシュされたパスワードのコピーを入手し、自分のマシンで「パスワードを試す」ことを想定しています。それは オフライン辞書攻撃 と呼ばれます。

あなたの場合、3番目の要素があります:検証キー。これはキーです。つまり、おそらく秘密です。 If攻撃者がソルトとハッシュされたパスワードを取得できるが、検証キーをできない場合、攻撃者は実行できません自分のマシンに対する辞書攻撃。 これらの条件下で(検証キーは秘密のままで、検証アルゴリズムは堅牢です-HMAC/SHA-512はそのために問題ありません)、設定可能な遅延PBKDF2の必要はありません。この種の秘密鍵による検証は、「peppering」と呼ばれることもあります。

ただし、攻撃者がハッシュされたパスワードのコピーを取得できると想定した場合、キーが悪意のある一瞥によってだまされたままであると想定することは、繊細さの問題になります。これはコンテキストに依存します。ほとんどのSQLインジェクション攻撃は、すべてのデータベースの一部を読み取ることができますが、マシン上の残りのファイルを読み取ることはできません。それにもかかわらず、サーバーは何らかの方法で起動して起動できる必要があるため、検証キーはディスクのどこかにあります。攻撃者がディスク全体(またはバックアップテープ...)を盗むと、検証キーも取得されます。この時点で、構成可能な速度の必要性に戻ります。

一般的に言えば、私はPBKDF2(別名Rfc2898DeriveBytes in .NET)カスタム構成を使用しますが、HMACを適切に使用しているように見えます(自家製の構成では、そのレベルの正確さが達成されることはめったにありません)。 「検証キー」を主張する場合(およびそのキーの特別なバックアップなど、キー管理の手続き上のオーバーヘッドを想定する準備ができている場合)、PBKDF2とを使用することをお勧めしますPBKDF2出力にHMACを適用します。

パスワードハッシュの詳細については、 この回答 を参照してください。

15
Tom Leek

私は最初の質問で学んだ教訓の編集に具体的に対応しています。

Rfc2898DeriveBytesは、1000回の反復で呼び出されます。 1024バイトの出力を使用します。 Dbのパスワードサイズは幸いにも2kに設計されています。ワークステーションでのテストの平均サンプルは約300ミリ秒でした。

要約:現在のCPU負荷とRfc2898DeriveBytesが必要な場合は、1000反復と1024バイト出力を52000反復と20バイト出力に変更します(20バイトはSHA-1のネイティブ出力です。これは.NET 4.5 Rfc2898DeriveBytesの出力です)に基づいています)。

参照を含む理由の説明:

攻撃者にあなたより有利になることを避けるために、使用されるハッシュ関数のネイティブ出力よりも大きい出力サイズでPBKDF2/RFC2898/PKCS#5(またはPBKDF2などで内部的に使用されるHMAC)を使用しないでください。 .NETの実装(4.5以降)はSHA-1をハードコード化しているため、8192ビットの出力ではなく、最大160ビットの出力を使用する必要があります。

これは、出力サイズ(dkLen、つまり派生キーの長さ)がネイティブハッシュ出力サイズ(hLen、つまりハッシュ長)より大きい場合、 RFC2898仕様 を参照しているためです。 9ページ目で、

ステップ2:「lを派生キーのhLenオクテットブロックの数にして、切り上げます」

ステップ3: "T_1 = F(P、S、c、1)、T_2 = F(P、S、c、2)、... T_l = F(P、S、c、l)、"

10ページ目:ステップ4: "DK = T_1 || T_2 || ... || T_l <0..r-1>"ここで、DKは派生キー(PBKDF2出力)であり、||連結演算子です。

したがって、8192ビットキーと.NETのHMAC-SHA-1の場合、8192/160 = 51.2ブロック、PBKDF2に必要なCEIL(51.2)= 52ブロック、つまりT_1からT_52(l = 52)が必要であることがわかります。 。フルブロックとは異なる.2の動作への仕様参照は、この説明の範囲外です(ヒント:完全な結果が計算された後の切り捨て)。

したがって、パスワードに対して1000回の反復のセットを合計52回実行し、出力を連結しています。したがって、1つのパスワードで、実際には52000回の反復が実行されています。

賢い攻撃者はたった1000回の反復を実行し、その160ビットの結果を8192ビットの出力の最初の160ビットと比較します-失敗した場合、それは間違った推測です。成功すれば、ほぼ確実に推測(攻撃)です。

したがって、CPUで52,000回の反復が実行されており、攻撃者は何を持っている場合でもおそらく1,000回の反復を実行しています(おそらく、最初はSHA-1のCPUを大幅に上回っているいくつかのGPU)。あなたは攻撃者にハードウェアの利点以上の52:1の利点を与えました。

ありがたいことに、優れたPBKDF2関数は調整が簡単です。出力長を160ビットに、反復回数を52,000に変更するだけで、同じ量のCPU時間を使用し、より小さなキーを格納し、ランタイムコストをかけずに、攻撃者にとって52倍の費用がかかるようになります。

GPUを使用して攻撃者をさらに攻撃したい場合は、PBKDF2-HMAC-SHA-512(またはscryptまたはbcrypt)および64バイト以下の出力サイズ(SHA-512のネイティブ出力サイズ)に切り替えることができます。これにより、64ビット命令がCPUにはあるがGPUにはないため、現在のGPUのCPUに対するアドバンテージの量が大幅に減少します。ただし、これは.NET 4.5ではネイティブで利用できません。

  • ただし、@ JitherはPBKDF2-HMAC-SHA256、PBKDF2-HMAC-SHA384、PBKDF2-HMAC-SHA512などの機能を.NETに追加する素晴らしい例を作成し、妥当なテストベクトルのセットを持つバリアントを 私のGithubリポジトリ 参照用。

1Passwordの実際の設計上の欠陥に関連する別のリファレンスについては、 このHashcatフォーラムのスレッド -を参照してください。PBKDF2-HMAC-SHA1の反復ごとに、SHA1変換を4回呼び出します。ただし、これは、 160ビットキー。必要な320ビットキーを生成するには、8回呼び出します。」

追伸選択した場合、小さな設計変更のために、Base64エンコードを実行する代わりにVARBINARYまたはBINARY列に出力を格納すると、データベーススペースを少し節約できます。

P.P.S.つまり、編集中のテストコードを次のように変更します(2000 * 52 = 104000)。あなたのテキストは1000を示し、あなたのテストは2000をリストしたので、テキストをテキストに、コードをコードに、それをモートすることに注意してください。

//var hash = libCrypto.HashEncode2(password, salt, 2000);
var hash = libCrypto.HashEncode2(password, salt, 104000);
// skip down into HashEncode2
//byte[] hash = deriver.GetBytes(1024);
byte[] hash = deriver.GetBytes(20);
10

SHA2ファミリーは、パスワードの保存には適していません。 md5よりもはるかに優れていますが、 実際にはbcryptを使用する必要があります (またはscrypt!)。

RNGCryptoServiceProvider はエントロピーの良い情報源です。理想的には、ソルトは64バイトではなく、バイト全体のように256ベースです。これをよりよく理解するには、Rainbowテーブルがどのように生成されるかを知る必要があります。 Rainbowテーブル生成への入力には、キースペースが必要です。たとえば、7〜12文字の英数字の記号を検索するためにRainbowを生成できます。次に、この提案されたソルトスキームを解読するために、攻撃者は64文字のソルト(大きい)を補うために、71〜76文字の英数字シンボルを生成する必要があります。ソルトを1バイトにすると、Rainbowテーブルが使い果たすキースペースが大幅に増加します。

4
rook

質問のRandomString関数にはバグがあると思います(いくつかの軽微な構文の問題に加えて)。長さパラメーターが256の因数でない場合は常にバイアスがあります。長さが62の場合、コードがオプションとして提案するように、「a」と「b」は他の文字よりも頻繁に発生します/ 62はすべての文字に対して分布し、aとbは他の文字の2倍の頻度である2/64になり、残りの60文字は1/64になります)。

バイアスがあるため、同じ塩を生成する可能性が高くなります。可能性は低いですが、あります。ただし、現状では、64文字すべてを使用しても問題ありません。

0
stevieg