C# による SHA256 と PBKDF2 によるソルト付きパスワードのハッシュ計算方法
ここでは C# でソルト付きのパスワードハッシュを生成する具体的な方法を紹介します。 今回は SHA256 を使う方法と、PBKDF2 を用いてストレッチングを行なう具体的な方法を示します。
パスワードハッシュの基本的な考え方については「ソルト付きパスワードハッシュとユーザー認証」をみてください。
RNGCryptoServiceProvider によるソルトの生成
ソルトを生成するには RNGCryptoServiceProvider を利用できます。
RNGCryptoServiceProvider は暗号サービスプロバイダ (Cryptographic Service Provider) の乱数生成用の関数です。
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);
}
RNGCryptoServiceProvider の GetBytes メソッドは、それに渡したバッファを暗号論的にランダムなデータで埋めます。これをソルトとします。
生成したソルトは Base64 で文字列として取得しています。
Base64 については Base64 エンコーディング方法 をみてください。 Base64 エンコードというのはバイナリデータを文字列として表現する方法のひとつです。
C# で SHA256 によるハッシュの計算
SHA256 によるハッシュの生成は次のようになります。
文字列でパスワードとソルトを受け取り、それらを結合してからハッシュを計算します。
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;
}
この GeneratePasswordHash メソッドではパスワードとする文字列と、ソルトとする文字列を渡したらそれらと繋げ、その値について SHA256 でハッシュを計算します。
SHA256 は任意の長さの入力に対して、256 ビット固定長のハッシュを返します。
PBKDF2 によるハッシュの計算
PBKDF2 というのは Password-Based Key Drivation Function 2 (パスワードベース鍵導出関数 2) の略で、 RSA 研究所の公開鍵暗号化標準仕様 (特に PKCS#5 パスワードに基づく暗号化の標準) の一部で、RFC 2898 として提案されている方法です。
この方法ではストレッチングのための、ハッシュの繰り返し計算回数を指定できます。
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;
}
コードの手間はほぼ同一であるにもかかわらず、安全性がグッとあがるなら採用しない手はありません。
ソルト生成とハッシュを生成する C# サンプルコード
時間計測用のコードを含め、以上をまとめると、次のようになります。
using System;
using static System.Console;
using System.Security.Cryptography;
using System.Text;
class Program
{
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 = "";
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 = "";
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;
}
public static void Main(string[] args)
{
var password = "password";
var salt = GenerateSalt();
// SHA256
var start1 = Environment.TickCount;
var hash1 = GeneratePasswordHash(password, salt);
var end1 = Environment.TickCount;
// PBKDF2
var start2 = Environment.TickCount;
var hash2 = GeneratePasswordHashPBKDF2(password, salt);
var end2 = Environment.TickCount;
WriteLine($"salt: {salt}");
WriteLine($"SHA256 hash: {hash1} ({end1 - start1}ms)");
WriteLine($"PBKDF2 hash: {hash2} ({end2 - start2}ms)");
}
}
システム運用時のハッシュの保存
暗号化アルゴリズム等は時を経るにつれより安全なものが推奨されていくものです。
現状は上述の PBKDF2 で 10,000 回の繰り返しによってハッシュを生成するので安全とされていますが、 今後、ハードウェアなどの発達によってはこれがもっと桁違いに繰り返さないといけなくなるかもしれません。
そうしたアルゴリズムの変更などに備えて、ハッシュを保存する際には、 PBKDF2:10000:[ソルト値]:[ハッシュ値] のように、使用したアルゴリズム、 繰り返し回数などの計算方法の情報も含め保存しておくと良いでしょう。