アプリケーションの登録フォームを作成していますが、C# を初めて使用するという問題がまだあります。
パスワードをmd5またはsha-256、できればsha-256に暗号化/ハッシュすることを検討しています。
良い例はありますか?「string password;」から情報を取得できるようにしたい それをハッシュして、変数「string hPassword;」に格納します。何か案は?
単純なハッシュやソルト ハッシュを使用しないでください。bcrypt (ここでは .NET 実装を使用) やPBKDF2 (組み込み実装を使用)などのキー強化手法を使用します。
PBKDF2 を使用した例を次に示します。
パスワードからキーを生成するには...
string password = GetPasswordFromUserInput();
// specify that we want to randomly generate a 20-byte salt
using (var deriveBytes = new Rfc2898DeriveBytes(password, 20))
{
byte[] salt = deriveBytes.Salt;
byte[] key = deriveBytes.GetBytes(20); // derive a 20-byte key
// save salt and key to database
}
そして、パスワードが有効かどうかをテストするには...
string password = GetPasswordFromUserInput();
byte[] salt, key;
// load salt and key from database
using (var deriveBytes = new Rfc2898DeriveBytes(password, salt))
{
byte[] newKey = deriveBytes.GetBytes(20); // derive a 20-byte key
if (!newKey.SequenceEqual(key))
throw new InvalidOperationException("Password is invalid!");
}
System.Security.Cryptography
名前空間を使用する必要があります。具体的には、MD5
classまたはSHA256
class .
このページのコードから少し引き出して、両方のクラスが同じ基本クラス ( HashAlgorithm
) を持っていることを知っていれば、次のような関数を使用できます。
public string ComputeHash(string input, HashAlgorithm algorithm)
{
Byte[] inputBytes = Encoding.UTF8.GetBytes(input);
Byte[] hashedBytes = algorithm.ComputeHash(inputBytes);
return BitConverter.ToString(hashedBytes);
}
次に、次のように呼び出すことができます (MD5 の場合):
string hPassword = ComputeHash(password, new MD5CryptoServiceProvider());
または SHA256 の場合:
string hPassword = ComputeHash(password, new SHA256CryptoServiceProvider());
編集: Salt サポートの追加
dtb がコメントで指摘したように、このコードにsaltを追加する機能が含まれていれば、このコードはより強力になります。ご存じない方のために説明すると、salt はハッシュ関数への入力として含まれるランダム ビットのセットであり、ハッシュ化されたパスワードに対する辞書攻撃 (たとえば、rainbow tableを使用) を阻止するのに大いに役立ちます。ComputeHash
これは、salt をサポートする関数の修正版です。
public static string ComputeHash(string input, HashAlgorithm algorithm, Byte[] salt)
{
Byte[] inputBytes = Encoding.UTF8.GetBytes(input);
// Combine salt and input bytes
Byte[] saltedInput = new Byte[salt.Length + inputBytes.Length];
salt.CopyTo(saltedInput, 0);
inputBytes.CopyTo(saltedInput, salt.Length);
Byte[] hashedBytes = algorithm.ComputeHash(saltedInput);
return BitConverter.ToString(hashedBytes);
}
これがお役に立てば幸いです!
パスワードをデータベースに保存するときは、ハッシュする前に常にパスワードをソルトする必要があります。
推奨されるデータベース列:
オンラインで見つけたほとんどの投稿では、ソルトとハッシュの ASCII エンコードについて説明していますが、それは必要なく、不要な計算を追加するだけです。また、SHA-1を使用する場合、出力は 20 バイトしかないため、データベース内のハッシュ フィールドの長さは 20 バイトで十分です。SHA-256 についてのご質問は理解できますが、やむを得ない理由がない限り、ほとんどのビジネス プラクティスでは SHA-1 とソルト値を使用するだけで十分です。SHA-256 に固執する場合、データベースのハッシュ フィールドの長さは 32 バイトである必要があります。
以下は、ソルトを生成し、ハッシュを計算し、パスワードに対してハッシュを検証するいくつかの関数です。
以下のソルト関数は、暗号的に作成された 4 つのランダム バイトから、暗号的に強力なソルトを整数として生成します。
private int GenerateSaltForPassword()
{
RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();
byte[] saltBytes = new byte[4];
rng.GetNonZeroBytes(saltBytes);
return (((int)saltBytes[0]) << 24) + (((int)saltBytes[1]) << 16) + (((int)saltBytes[2]) << 8) + ((int)saltBytes[3]);
}
次に、以下の関数でソルトを使用してパスワードをハッシュできます。ソルトはパスワードに連結され、ハッシュが計算されます。
private byte[] ComputePasswordHash(string password, int salt)
{
byte[] saltBytes = new byte[4];
saltBytes[0] = (byte)(salt >> 24);
saltBytes[1] = (byte)(salt >> 16);
saltBytes[2] = (byte)(salt >> 8);
saltBytes[3] = (byte)(salt);
byte[] passwordBytes = UTF8Encoding.UTF8.GetBytes(password);
byte[] preHashed = new byte[saltBytes.Length + passwordBytes.Length];
System.Buffer.BlockCopy(passwordBytes, 0, preHashed, 0, passwordBytes.Length);
System.Buffer.BlockCopy(saltBytes, 0, preHashed, passwordBytes.Length, saltBytes.Length);
SHA1 sha1 = SHA1.Create();
return sha1.ComputeHash(preHashed);
}
パスワードのチェックは、ハッシュを計算し、予想されるハッシュと比較するだけで実行できます。
private bool IsPasswordValid(string passwordToValidate, int salt, byte[] correctPasswordHash)
{
byte[] hashedPassword = ComputePasswordHash(passwordToValidate, salt);
return hashedPassword.SequenceEqual(correctPasswordHash);
}
これは、持続性を認識しない SecuredPassword クラスの完全な実装です。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
public class SecuredPassword
{
private const int saltSize = 256;
private readonly byte[] hash;
private readonly byte[] salt;
public byte[] Hash
{
get { return hash; }
}
public byte[] Salt
{
get { return salt; }
}
public SecuredPassword(string plainPassword)
{
if (string.IsNullOrWhiteSpace(plainPassword))
return;
using (var deriveBytes = new Rfc2898DeriveBytes(plainPassword, saltSize))
{
salt = deriveBytes.Salt;
hash = deriveBytes.GetBytes(saltSize);
}
}
public SecuredPassword(byte[] hash, byte[] salt)
{
this.hash = hash;
this.salt = salt;
}
public bool Verify(string password)
{
if (string.IsNullOrWhiteSpace(password))
return false;
using (var deriveBytes = new Rfc2898DeriveBytes(password, salt))
{
byte[] newKey = deriveBytes.GetBytes(saltSize);
return newKey.SequenceEqual(hash);
}
}
}
そしてテスト:
public class SecuredPasswordTests
{
[Test]
public void IsHashed_AsExpected()
{
var securedPassword = new SecuredPassword("password");
Assert.That(securedPassword.Hash, Is.Not.EqualTo("password"));
Assert.That(securedPassword.Hash.Length, Is.EqualTo(256));
}
[Test]
public void Generates_Unique_Salt()
{
var securedPassword = new SecuredPassword("password");
var securedPassword2 = new SecuredPassword("password");
Assert.That(securedPassword.Salt, Is.Not.Null);
Assert.That(securedPassword2.Salt, Is.Not.Null);
Assert.That(securedPassword.Salt, Is.Not.EqualTo(securedPassword2.Salt));
}
[Test]
public void Generates_Unique_Hash()
{
var securedPassword = new SecuredPassword("password");
var securedPassword2 = new SecuredPassword("password");
Assert.That(securedPassword.Hash, Is.Not.Null);
Assert.That(securedPassword2.Hash, Is.Not.Null);
Assert.That(securedPassword.Hash, Is.Not.EqualTo(securedPassword2.Hash));
}
[Test]
public void Verify_WhenMatching_ReturnsTrue()
{
var securedPassword = new SecuredPassword("password");
var result = securedPassword.Verify("password");
Assert.That(result, Is.True);
}
[Test]
public void Verify_WhenDifferent_ReturnsFalse()
{
var securedPassword = new SecuredPassword("password");
var result = securedPassword.Verify("Password");
Assert.That(result, Is.False);
}
[Test]
public void Verify_WhenRehydrated_AndMatching_ReturnsTrue()
{
var securedPassword = new SecuredPassword("password123");
var rehydrated = new SecuredPassword(securedPassword.Hash, securedPassword.Salt);
var result = rehydrated.Verify("password123");
Assert.That(result, Is.True);
}
[Test]
public void Constructor_Handles_Null_Password()
{
Assert.DoesNotThrow(() => new SecuredPassword(null));
}
[Test]
public void Constructor_Handles_Empty_Password()
{
Assert.DoesNotThrow(() => new SecuredPassword(string.Empty));
}
[Test]
public void Verify_Handles_Null_Password()
{
Assert.DoesNotThrow(() => new SecuredPassword("password").Verify(null));
}
[Test]
public void Verify_Handles_Empty_Password()
{
Assert.DoesNotThrow(() => new SecuredPassword("password").Verify(string.Empty));
}
[Test]
public void Verify_When_Null_Password_ReturnsFalse()
{
Assert.That(new SecuredPassword("password").Verify(null), Is.False);
}
}
ハッシュ化されたパスワードを保存する場合は、SHA-256 の代わりにbcryptを使用してください。問題は、SHA-256 が速度を重視して最適化されているため、誰かがデータベースにアクセスした場合に、パスワードに対するブルート フォース攻撃が容易になることです。
この記事を読んでください:レインボー テーブルで十分: 安全なパスワード スキームについて知っておくべきことと、以前の SO の質問に対するこの回答。
記事からの引用:
問題は、MD5 が高速であることです。SHA1 や SHA256 などの最新の競合他社も同様です。ハッシュはほとんどすべての暗号システムの構成要素であり、通常はパケットごとまたはメッセージごとに要求実行されるため、速度は最新の安全なハッシュの設計目標です。
速度は、パスワード ハッシュ関数で望ましくないものです。
最後に、パスワードを安全に保存するには、PHK の MD5 スキーム、Provos-Maziere の Bcrypt スキーム、および SRP の 3 つの合理的なオプションがあることを学びました。正しい選択は Bcrypt であることがわかりました。
PBKDF2 は HMACSHA1 を使用しています.......もっと最新の HMACSHA256 または HMACSHA512 の実装が必要で、アルゴリズムを遅くするためにキーストレッチが必要な場合は、この API をお勧めします: https://sourceforge.net/projects/pwdtknet/
System.Security.Cryptography.SHA256 クラスがそのトリックを行う必要があります。
http://msdn.microsoft.com/en-us/library/system.security.cryptography.sha256.aspx