C# 入門

ホーム > C# の各種トピック > SHA256 と PBKDF2 によるソルト付きパスワードのハッシュ計算方法

SHA256 と PBKDF2 によるソルト付きパスワードのハッシュ計算方法

ここでは C# でソルト付きのパスワードハッシュを生成する具体的な方法を紹介します。

パスワードハッシュの基本的な考え方がわからない、という方は「パスワードハッシュとユーザー認証」をみてください。

今回は SHA256 を使う方法と、PBKDF2 を用いてストレッチングを行なう具体的な方法を示します。

RNGCryptoServiceProvider によるソルトの生成と SHA256 によるハッシュの計算

まずはソルトの生成箇所と SHA256 によるハッシュの生成は次のようになります。

using System;
using System.Diagnostics;
using System.Security.Cryptography;
using System.Text;

namespace Keicode.Security {
  class HashUtil {

    const int SALT_SIZE = 24;

    public static string GenerateSalt() {
      var buff = new byte[SALT_SIZE];
      using( var rng = new RNGCryptoServiceProvider() ) {
        rng.GetBytes( buff );
      }
      return Convert.ToBase64String( buff );
    }

    public static string GeneratePasswordHash(
      string pwd, string salt ) {
      var result = "";
      var saltAndPwd = String.Concat( pwd, salt );
      var encoder = new UTF8Encoding();
      var buffer = encoder.GetBytes( saltAndPwd );
      using( var csp = new SHA256CryptoServiceProvider() ) {
        var hash = csp.ComputeHash( buffer );
        result = Convert.ToBase64String( hash );
      }
      return result;
    }
    ...
  }
}

RNGCryptoServiceProvider の GetBytes メソッドは、それに渡したバッファを暗号論的にランダムなデータで埋めます。

上記の GeneratePasswordHash メソッドではパスワードとする文字列と、ソルトとする文字列を渡したらそれらと繋げ、その値について SHA256 でハッシュを計算します。

SHA256 は任意の長さの入力に対して、256 ビット固定長のハッシュを返します。

PBKDF2 によるハッシュの計算

PBKDF2 というのは Password-Based Key Drivation Function 2 (パスワードベース鍵導出関数 2) の略で、 RSA 研究所の公開鍵暗号化標準仕様 (特に PKCS#5 パスワードに基づく暗号化の標準) の一部で、RFC 2898 として提案されている方法です。

この方法ではストレッチングのための、ハッシュの繰り返し計算回数を指定できます。

using System;
using System.Diagnostics;
using System.Security.Cryptography;
using System.Text;

namespace Keicode.Security {
  class HashUtil {

    const int SALT_SIZE = 24;
    const int PBKDF2_ITERATION = 10000;

    ...

    public static string GeneratePasswordHashPBKDF2(
      string pwd, string salt ) {
      var result = "";
      var encoder = new UTF8Encoding();
      var b = new Rfc2898DeriveBytes(
        pwd, encoder.GetBytes( salt ), PBKDF2_ITERATION );
      var k = b.GetBytes( 32 );
      result = Convert.ToBase64String( k );
      return result;
    }

  }
}

コードの手間はほぼ同一であるにもかかわらず、安全性がグッとあがるなら採用しない手はありませんね。

尚、実際にハッシュの計算にどのくらい時間がかかるか上記 SHA256 と PBKDF2 (繰り返し 10,000 回) とで Tick Count を確認しました。

SHA256 を利用した場合は、3回計算してどれも 1ms 以下でした。

一方、PBKDF2 では次のように一度の計算で 180ms 前後かかっています。

ソルト生成、SHA256、PBKDF2 (プラス、時間計測用コード) のサンプル

時間計測用のコードを含め、以上をまとめると、次のようになります。

using System;
using System.Diagnostics;
using System.Security.Cryptography;
using System.Text;

namespace Keicode.Security {
  class HashUtil {

    const int SALT_SIZE = 24;
    const int PBKDF2_ITERATION = 10000;

    public static string GenerateSalt() {
      var buff = new byte[SALT_SIZE];
      using( var rng = new RNGCryptoServiceProvider() ) {
        rng.GetBytes( buff );
      }
      return Convert.ToBase64String( buff );
    }

    public static string GeneratePasswordHash(
      string pwd, string salt ) {
      var result = "";
      using( var c = new MyCounter("SHA256") ) {
        var saltAndPwd = String.Concat( pwd, salt );
        var encoder = new UTF8Encoding();
        var buffer = encoder.GetBytes( saltAndPwd );
        using( var csp = new SHA256CryptoServiceProvider() ) {
          var hash = csp.ComputeHash( buffer );
          result = Convert.ToBase64String( hash );
        }
      }
      return result;
    }

    public static string GeneratePasswordHashPBKDF2(
      string pwd, string salt ) {
      var result = "";
      using( var c = new MyCounter( "PBKDF2" ) ) {
        var encoder = new UTF8Encoding();
        var b = new Rfc2898DeriveBytes(
          pwd, encoder.GetBytes( salt ), PBKDF2_ITERATION );
        var k = b.GetBytes( 32 );
        result = Convert.ToBase64String( k );
      }
      return result;
    }

    class MyCounter : IDisposable {
      int start;
      int end;
      string tag = "MyCounter";

      public MyCounter( string tag ) {
        this.tag = tag;
        this.start = Environment.TickCount;
      }

      public void Dispose() {
        this.end = Environment.TickCount;
        Debug.WriteLine( "{0}: {1}[ms]",
          this.tag, this.end - this.start );
      }
    }
  }
}

尚、コードの中で突如出てきた Base64 ってなんなの?という方は Base64 エンコーディング方法 をみてください。 Base64 エンコードというのはバイナリデータを扱う際にはよく登場しますし、バイナリのデータを文字として表現できるので便利です。

それから、暗号化アルゴリズム等は時を経るにつれより安全なものが推奨されていくものです。現状は上述の PBKDF2 で 10,000 回の繰り返しによってハッシュを生成するので安全とされていますが、 今後、ハードウェアなどの発達によってはこれがもっと桁違いに繰り返さないといけなくなるかもしれません。

そうしたアルゴリズムの変更などに備えて、ハッシュを保存する際に、"PBKDF2:10000:[ソルト値]:[ハッシュ値]" のように計算方法も含め保存しておくと良いでしょう。

ホーム > C# の各種トピック > SHA256 と PBKDF2 によるソルト付きパスワードのハッシュ計算方法