パスワードの保存には[PBKDF2 http://en.wikipedia.org/wiki/PBKDF2 ]が好いと聞いた。 cf. [エフセキュアブログ : 「SHA-1+salt」はパスワードに十分だと思いますか? http://blog.f-secure.jp/archives/50564743.html ]
パスワード+salt のハッシュ化 (SHA-1) に依ってレインボーテーブル攻撃に強くする事が出来る。HMAC を使うのが簡便だ。総当り (ブルートフォースアタック) にも強くするには、streaching を行うのが好い。此れには PBKDF2 を使うのが簡便である。PBKDF2 は内部で HMAC を使用する。 此う云う事は数学的に追ってみないと理解出来ない。私は理解出来ていない。 実装してみる。
Window1.xaml
|xml| <Window x:Class="PasswordTest.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="PBKDF2 (Rfc2898DeriveBytes)" Height="338" Width="664">
<Grid>
<Label Height="28" HorizontalAlignment="Left" Margin="36,18,0,0" Name="labelUserID" VerticalAlignment="Top" Width="56">User</Label>
<TextBox Height="24" Margin="97,22,261,0" Name="textBoxUserID" VerticalAlignment="Top" TextChanged="textBoxUserID_TextChanged" MaxLength="32" />
<Label Height="28" HorizontalAlignment="Left" Margin="36,51,0,0" Name="labelPass" VerticalAlignment="Top" Width="56">Pass</Label>
<TextBox Height="24" Margin="97,51,261,0" Name="textBoxPass" VerticalAlignment="Top" TextChanged="textBoxPass_TextChanged" MaxLength="256" />
<Button Height="23" Margin="306,80,261,0" Name="buttonResist" VerticalAlignment="Top" Click="buttonResist_Click" IsEnabled="False">Resist</Button>
<Label Height="28" HorizontalAlignment="Left" Margin="36,0,0,116" Name="labelSalt" VerticalAlignment="Bottom" Width="56">Salt</Label>
<TextBox Height="24" Margin="97,0,261,120" Name="textBoxSalt" VerticalAlignment="Bottom" />
<Label Height="28" Margin="36,0,550,83" Name="labelPbkdf2" VerticalAlignment="Bottom">PBKDF2</Label>
<TextBox Height="24" Margin="0,0,261,83" Name="textBoxPbkdf2" VerticalAlignment="Bottom" HorizontalAlignment="Right" Width="284" />
<Label Height="28" Margin="0,22,37,0" Name="labelValidateUserID" VerticalAlignment="Top" Visibility="Hidden" HorizontalAlignment="Right" Width="219">^[A-Za-z](?:[A-Za-z0-9]+[-_.]?)+$</Label>
<Label Height="28" Margin="0,51,147,0" Name="labelValidatePass" VerticalAlignment="Top" Visibility="Hidden" HorizontalAlignment="Right" Width="109">^[ -~]{8,256}$</Label>
<Label Height="28" HorizontalAlignment="Left" Margin="97,0,0,50" Name="labelTime" VerticalAlignment="Bottom" Width="185"></Label>
</Grid>
||<
Window1.cs
|cs| using System; using System.Security.Cryptography; using System.Text.RegularExpressions; using System.Threading; using System.Windows; using System.Windows.Controls;
namespace PasswordTest
{
///
private bool ValidateUserID(string userID)
{
if (8 <= userID.Length && userID.Length <= 32 && Regex.IsMatch(userID, "^[A-Za-z](?:[A-Za-z0-9]+[-_.]?)+$"))
return true;
return false;
}
private bool ValidatePass(string pass)
{
if (Regex.IsMatch(pass, "^[ -~]{8,256}$")) return true;
return false;
}
private bool ShowInputValidation()
{
bool isValid = true;
if (ValidateUserID(textBoxUserID.Text)) labelValidateUserID.Visibility = Visibility.Hidden;
else
{
labelValidateUserID.Visibility = Visibility.Visible;
isValid = false;
}
if (ValidatePass(textBoxPass.Text)) labelValidatePass.Visibility = Visibility.Hidden;
else
{
labelValidatePass.Visibility = Visibility.Visible;
isValid = false;
}
if (isValid) buttonResist.IsEnabled = true;
else buttonResist.IsEnabled = false;
return isValid;
}
private byte[] GenerateSalt()
{
Guid guid = Guid.NewGuid();
return guid.ToByteArray();
}
private byte[] GenerateHash(string pass, byte[] salt)
{
var key = new Rfc2898DeriveBytes(pass, salt, 100000);
return key.GetBytes(16);
}
private string ConvertByteArrayToString(byte[] sequence)
{
return Convert.ToBase64String(sequence);
}
private void ResistThreadStart()
{
var startTime = DateTime.Now;
byte[] salt = GenerateSalt();
byte[] hash = GenerateHash(Pass, salt);
var endTime = DateTime.Now;
Dispatcher.BeginInvoke((Action)delegate()
{
textBoxSalt.Text = ConvertByteArrayToString(salt);
textBoxPbkdf2.Text = ConvertByteArrayToString(hash);
labelTime.Content = endTime - startTime;
});
}
public Window1()
{
InitializeComponent();
}
private void buttonResist_Click(object sender, RoutedEventArgs e)
{
bool isValid = ShowInputValidation();
if (!isValid) return;
UserID = textBoxUserID.Text;
Pass = textBoxPass.Text;
var thread = new Thread(new ThreadStart(ResistThreadStart));
thread.IsBackground = true;
thread.Start();
}
private void textBoxUserID_TextChanged(object sender, TextChangedEventArgs e)
{
ShowInputValidation();
}
private void textBoxPass_TextChanged(object sender, TextChangedEventArgs e)
{
ShowInputValidation();
}
}
}
||<
System.Security.Cryptography.Rfc2898DeriveBytes
を使う。ハッシュ関数は十万回繰り返している。私の CPU (Corei5 2.67GHz) では 0.7 秒程度掛かる。