C# パスワードを暗号化して保存する(PBKDF2)

WEBサービスなどユーザーのログイン処理を作成する場合、各ユーザーのパスワードをデータベースなどに保存する必要があります。

このとき、パスワードは正しい方法で暗号化しておく必要があります。

  • 暗号化せずにパスワードを保存 → パスワードが漏えいする危険性大(NG)
  • 3DESやAESで暗号化して保存 → 暗号キーが漏えいすると元のパスワードに複号される(NG)
  • MD5やSHA-1でハッシュ値を保存 → ハッシュ値も容易に元のパスワードを復元可能(NG)

さらに、複数のユーザーが同じパスワードを設定している際にまったく同じ値が保存されていると、パスワードを推測される可能性があります(これはソルトを付加することで対策します)。

また、機械的な総当たり攻撃への対策も必要になります(これはストレッチング(暗号化の繰り返し)という方法で対策します)。

では、実際にどのような方法で暗号化すれば良いかというと、.NET Framework ではRfc2898DeriveBytesクラスを使用した暗号キー(PBKDF2)をお勧めします。ソルトやストレッチングに対応しているため、実装自体は非常に簡単です。

ソルトの生成するメソッド

public static byte[] CreateSalt(int size)
{
    var bytes = new byte[size];
    using (var rngCryptoServiceProvider = new RNGCryptoServiceProvider())
    {
        rngCryptoServiceProvider.GetBytes(bytes);
    }
    return bytes;
}

PBKDF2によるハッシュ値を生成するメソッド

private static byte[] CreatePBKDF2Hash(string password, byte[] salt, int size, int iteration)
{
    using (var rfc2898DeriveBytes = new Rfc2898DeriveBytes(password, salt, iteration))
    {
        return rfc2898DeriveBytes.GetBytes(size);
    } 
}

上記2つのメソッドを使ってパスワード文字列からハッシュ値を生成

int saltSize = 32;      // ソルトのサイズ
int hashSize = 32;      // ハッシュサイズ
int iteration = 10000;  // ストレッチングの回数
 
// ソルトを生成する
byte[] saltBytes = CreateSalt(saltSize);
 
// PBKDF2によるハッシュを生成
byte[] hashBytes = CreatePBKDF2Hash("p@ssword", saltBytes, hashSize, iteration);
 
// Base64 文字列に変換
string saltText = Convert.ToBase64String(saltBytes);
string hashText = Convert.ToBase64String(hashBytes);
 
// データベース等に salt, hash, iteration を保存しておく

ログイン処理などで認証を行う際は入力された文字列と保存していたsaltとiterationを使用してハッシュを生成して比較します(iterationはある程度大きな値で固定でも良いかもしれません)。

// 認証の際は入力された文字列と保存していたsaltを使用してハッシュを生成
byte[] inputHashBytes = CreatePBKDF2Hash("p@ssword", saltBytes, hashSize, iteration);
string inputHashText = Convert.ToBase64String(inputHashBytes);
 
// 保存していたハッシュと入力文字から生成したハッシュを比較して認証を行う
if (hashText == inputHashText)
{
    // ログイン処理
}
使用環境: Visual Studio 2015 .NET Framework 4.5

C# メニューリスト