回答:
更新:この回答は深刻に古くなっています。代わりに、https://stackoverflow.com/a/10402129/251311の推奨事項を使用してください。
あなたはどちらかを使うことができます
var md5 = new MD5CryptoServiceProvider();
var md5data = md5.ComputeHash(data);
または
var sha1 = new SHA1CryptoServiceProvider();
var sha1data = sha1.ComputeHash(data);
data
あなたが使用できるバイト配列として取得するには
var data = Encoding.ASCII.GetBytes(password);
md5data
またはから文字列を取得するsha1data
var hashedPassword = ASCIIEncoding.GetString(md5data);
md5
、ほとんどすべての種類のタスクには十分です。その脆弱性は非常に特定の状況にも関係しており、攻撃者が暗号化について多くのことを知っている必要があります。
ここでの他の回答のほとんどは、今日のベストプラクティスでは多少古くなっています。そのため、ここにPBKDF2 / Rfc2898DeriveBytes
を使用してパスワードを保存および検証するアプリケーションがあります。次のコードは、この投稿のスタンドアロンクラスにあります。ソルトパスワードハッシュを格納する方法の別の例。。基本は本当に簡単なので、ここで分解します:
ステップ1暗号化PRNGを使用してソルト値を作成します。
byte[] salt;
new RNGCryptoServiceProvider().GetBytes(salt = new byte[16]);
ステップ2 Rfc2898DeriveBytesを作成し、ハッシュ値を取得します。
var pbkdf2 = new Rfc2898DeriveBytes(password, salt, 100000);
byte[] hash = pbkdf2.GetBytes(20);
ステップ3後で使用するためにソルトとパスワードのバイトを結合します。
byte[] hashBytes = new byte[36];
Array.Copy(salt, 0, hashBytes, 0, 16);
Array.Copy(hash, 0, hashBytes, 16, 20);
ステップ4組み合わせたsalt + hashを保存用の文字列に変換します
string savedPasswordHash = Convert.ToBase64String(hashBytes);
DBContext.AddUser(new User { ..., Password = savedPasswordHash });
ステップ5ユーザーが入力したパスワードを、保存されているパスワードと照合します。
/* Fetch the stored value */
string savedPasswordHash = DBContext.GetUser(u => u.UserName == user).Password;
/* Extract the bytes */
byte[] hashBytes = Convert.FromBase64String(savedPasswordHash);
/* Get the salt */
byte[] salt = new byte[16];
Array.Copy(hashBytes, 0, salt, 0, 16);
/* Compute the hash on the password the user entered */
var pbkdf2 = new Rfc2898DeriveBytes(password, salt, 100000);
byte[] hash = pbkdf2.GetBytes(20);
/* Compare the results */
for (int i=0; i < 20; i++)
if (hashBytes[i+16] != hash[i])
throw new UnauthorizedAccessException();
注:特定のアプリケーションのパフォーマンス要件によっては、値100000
を減らすことができます。最小値はおよそ10000
です。
csharptest.netのすばらしい答えに基づいて、私はこれのためのクラスを書きました:
public static class SecurePasswordHasher
{
/// <summary>
/// Size of salt.
/// </summary>
private const int SaltSize = 16;
/// <summary>
/// Size of hash.
/// </summary>
private const int HashSize = 20;
/// <summary>
/// Creates a hash from a password.
/// </summary>
/// <param name="password">The password.</param>
/// <param name="iterations">Number of iterations.</param>
/// <returns>The hash.</returns>
public static string Hash(string password, int iterations)
{
// Create salt
byte[] salt;
new RNGCryptoServiceProvider().GetBytes(salt = new byte[SaltSize]);
// Create hash
var pbkdf2 = new Rfc2898DeriveBytes(password, salt, iterations);
var hash = pbkdf2.GetBytes(HashSize);
// Combine salt and hash
var hashBytes = new byte[SaltSize + HashSize];
Array.Copy(salt, 0, hashBytes, 0, SaltSize);
Array.Copy(hash, 0, hashBytes, SaltSize, HashSize);
// Convert to base64
var base64Hash = Convert.ToBase64String(hashBytes);
// Format hash with extra information
return string.Format("$MYHASH$V1${0}${1}", iterations, base64Hash);
}
/// <summary>
/// Creates a hash from a password with 10000 iterations
/// </summary>
/// <param name="password">The password.</param>
/// <returns>The hash.</returns>
public static string Hash(string password)
{
return Hash(password, 10000);
}
/// <summary>
/// Checks if hash is supported.
/// </summary>
/// <param name="hashString">The hash.</param>
/// <returns>Is supported?</returns>
public static bool IsHashSupported(string hashString)
{
return hashString.Contains("$MYHASH$V1$");
}
/// <summary>
/// Verifies a password against a hash.
/// </summary>
/// <param name="password">The password.</param>
/// <param name="hashedPassword">The hash.</param>
/// <returns>Could be verified?</returns>
public static bool Verify(string password, string hashedPassword)
{
// Check hash
if (!IsHashSupported(hashedPassword))
{
throw new NotSupportedException("The hashtype is not supported");
}
// Extract iteration and Base64 string
var splittedHashString = hashedPassword.Replace("$MYHASH$V1$", "").Split('$');
var iterations = int.Parse(splittedHashString[0]);
var base64Hash = splittedHashString[1];
// Get hash bytes
var hashBytes = Convert.FromBase64String(base64Hash);
// Get salt
var salt = new byte[SaltSize];
Array.Copy(hashBytes, 0, salt, 0, SaltSize);
// Create hash with given salt
var pbkdf2 = new Rfc2898DeriveBytes(password, salt, iterations);
byte[] hash = pbkdf2.GetBytes(HashSize);
// Get result
for (var i = 0; i < HashSize; i++)
{
if (hashBytes[i + SaltSize] != hash[i])
{
return false;
}
}
return true;
}
}
使用法:
// Hash
var hash = SecurePasswordHasher.Hash("mypassword");
// Verify
var result = SecurePasswordHasher.Verify("mypassword", hash);
サンプルハッシュは次のようになります。
$MYHASH$V1$10000$Qhxzi6GNu/Lpy3iUqkeqR/J1hh8y/h5KPDjrv89KzfCVrubn
ご覧のとおり、簡単に使用できるようにハッシュに反復を含めており、アップグレードが必要な場合にこれをアップグレードする可能性もあります。
.netコアに興味がある場合は、Code Reviewに .netコアバージョンもあります。
V1
とV2
あなたが必要とする検証方法います。
私はパスワードの暗号化にハッシュとソルトを使用します(Asp.Netメンバーシップが使用するのと同じハッシュです):
private string PasswordSalt
{
get
{
var rng = new RNGCryptoServiceProvider();
var buff = new byte[32];
rng.GetBytes(buff);
return Convert.ToBase64String(buff);
}
}
private string EncodePassword(string password, string salt)
{
byte[] bytes = Encoding.Unicode.GetBytes(password);
byte[] src = Encoding.Unicode.GetBytes(salt);
byte[] dst = new byte[src.Length + bytes.Length];
Buffer.BlockCopy(src, 0, dst, 0, src.Length);
Buffer.BlockCopy(bytes, 0, dst, src.Length, bytes.Length);
HashAlgorithm algorithm = HashAlgorithm.Create("SHA1");
byte[] inarray = algorithm.ComputeHash(dst);
return Convert.ToBase64String(inarray);
}
public class CryptographyProcessor
{
public string CreateSalt(int size)
{
//Generate a cryptographic random number.
RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();
byte[] buff = new byte[size];
rng.GetBytes(buff);
return Convert.ToBase64String(buff);
}
public string GenerateHash(string input, string salt)
{
byte[] bytes = Encoding.UTF8.GetBytes(input + salt);
SHA256Managed sHA256ManagedString = new SHA256Managed();
byte[] hash = sHA256ManagedString.ComputeHash(bytes);
return Convert.ToBase64String(hash);
}
public bool AreEqual(string plainTextInput, string hashedInput, string salt)
{
string newHashedPin = GenerateHash(plainTextInput, salt);
return newHashedPin.Equals(hashedInput);
}
}
@ csharptest.netとChristian Gollhardtの回答は素晴らしいです。ありがとうございました。しかし、このコードを何百万ものレコードを含む本番環境で実行した後、メモリリークがあることに気付きました。RNGCryptoServiceProviderクラスとRfc2898DeriveBytesクラスはIDisposableから派生していますが、それらは破棄しません。誰かが処分バージョンを必要とする場合、私は答えとして私の解決策を書きます。
public static class SecurePasswordHasher
{
/// <summary>
/// Size of salt.
/// </summary>
private const int SaltSize = 16;
/// <summary>
/// Size of hash.
/// </summary>
private const int HashSize = 20;
/// <summary>
/// Creates a hash from a password.
/// </summary>
/// <param name="password">The password.</param>
/// <param name="iterations">Number of iterations.</param>
/// <returns>The hash.</returns>
public static string Hash(string password, int iterations)
{
// Create salt
using (var rng = new RNGCryptoServiceProvider())
{
byte[] salt;
rng.GetBytes(salt = new byte[SaltSize]);
using (var pbkdf2 = new Rfc2898DeriveBytes(password, salt, iterations))
{
var hash = pbkdf2.GetBytes(HashSize);
// Combine salt and hash
var hashBytes = new byte[SaltSize + HashSize];
Array.Copy(salt, 0, hashBytes, 0, SaltSize);
Array.Copy(hash, 0, hashBytes, SaltSize, HashSize);
// Convert to base64
var base64Hash = Convert.ToBase64String(hashBytes);
// Format hash with extra information
return $"$HASH|V1${iterations}${base64Hash}";
}
}
}
/// <summary>
/// Creates a hash from a password with 10000 iterations
/// </summary>
/// <param name="password">The password.</param>
/// <returns>The hash.</returns>
public static string Hash(string password)
{
return Hash(password, 10000);
}
/// <summary>
/// Checks if hash is supported.
/// </summary>
/// <param name="hashString">The hash.</param>
/// <returns>Is supported?</returns>
public static bool IsHashSupported(string hashString)
{
return hashString.Contains("HASH|V1$");
}
/// <summary>
/// Verifies a password against a hash.
/// </summary>
/// <param name="password">The password.</param>
/// <param name="hashedPassword">The hash.</param>
/// <returns>Could be verified?</returns>
public static bool Verify(string password, string hashedPassword)
{
// Check hash
if (!IsHashSupported(hashedPassword))
{
throw new NotSupportedException("The hashtype is not supported");
}
// Extract iteration and Base64 string
var splittedHashString = hashedPassword.Replace("$HASH|V1$", "").Split('$');
var iterations = int.Parse(splittedHashString[0]);
var base64Hash = splittedHashString[1];
// Get hash bytes
var hashBytes = Convert.FromBase64String(base64Hash);
// Get salt
var salt = new byte[SaltSize];
Array.Copy(hashBytes, 0, salt, 0, SaltSize);
// Create hash with given salt
using (var pbkdf2 = new Rfc2898DeriveBytes(password, salt, iterations))
{
byte[] hash = pbkdf2.GetBytes(HashSize);
// Get result
for (var i = 0; i < HashSize; i++)
{
if (hashBytes[i + SaltSize] != hash[i])
{
return false;
}
}
return true;
}
}
}
使用法:
// Hash
var hash = SecurePasswordHasher.Hash("mypassword");
// Verify
var result = SecurePasswordHasher.Verify("mypassword", hash);
KeyDerivation.Pbkdf2の使用はRfc2898DeriveBytesよりも優れていると思います。
例と説明: ASP.NET Coreのハッシュパスワード
using System;
using System.Security.Cryptography;
using Microsoft.AspNetCore.Cryptography.KeyDerivation;
public class Program
{
public static void Main(string[] args)
{
Console.Write("Enter a password: ");
string password = Console.ReadLine();
// generate a 128-bit salt using a secure PRNG
byte[] salt = new byte[128 / 8];
using (var rng = RandomNumberGenerator.Create())
{
rng.GetBytes(salt);
}
Console.WriteLine($"Salt: {Convert.ToBase64String(salt)}");
// derive a 256-bit subkey (use HMACSHA1 with 10,000 iterations)
string hashed = Convert.ToBase64String(KeyDerivation.Pbkdf2(
password: password,
salt: salt,
prf: KeyDerivationPrf.HMACSHA1,
iterationCount: 10000,
numBytesRequested: 256 / 8));
Console.WriteLine($"Hashed: {hashed}");
}
}
/*
* SAMPLE OUTPUT
*
* Enter a password: Xtw9NMgx
* Salt: NZsP6NnmfBuYeJrrAKNuVQ==
* Hashed: /OOoOer10+tGwTRDTrQSoeCxVTFr6dtYly7d0cPxIak=
*/
これは、記事のサンプルコードです。そしてそれは最低限のセキュリティレベルです。これを増やすには、KeyDerivationPrf.HMACSHA1パラメータの代わりに使用します
KeyDerivationPrf.HMACSHA256またはKeyDerivationPrf.HMACSHA512。
パスワードハッシュについて妥協しないでください。パスワードハッシュハッキングを最適化するには、数学的に適切な方法が多数あります。結果は悲惨な可能性があります。悪意のあるユーザーがユーザーのパスワードハッシュテーブルを手に入れたら、アルゴリズムが弱いか、実装が正しくない場合、比較的簡単にパスワードを解読できます。彼はパスワードを解読するために多くの時間(時間xコンピュータ能力)を持っています。「大量の時間」を「不合理な時間」に変えるために、パスワードのハッシュは暗号的に強力でなければなりません。
追加するもう1つのポイント
ハッシュの検証には時間がかかります(そしてそれは良いことです)。ユーザーが間違ったユーザー名を入力した場合、ユーザー名が正しくないことを確認するのに時間がかかりません。ユーザー名が正しい場合、パスワードの検証を開始します。これは比較的長いプロセスです。
ハッカーにとって、ユーザーが存在するかどうかを理解するのは非常に簡単です。
ユーザー名が間違っている場合は、すぐに返答しないようにしてください。
言うまでもないことですが、何が悪いのか答えてはいけません。一般的な「資格情報が間違っている」だけです。
using
ステートメントに入れるか、呼び出しClear()
てください。