私はアプリケーション用の登録フォームを書いていますが、c#が初めてであることにまだ問題があります。
パスワードをmd5またはsha-256、できればsha-256に暗号化/ハッシュ化しようとしています。
良い例はありますか? 「string password」から情報を取得できるようにします。次に、ハッシュして変数「string hPassword;」に保存します。何か案は?
単純なハッシュ、またはソルトハッシュを使用しないでください。 bcrypt (ここでは 。NET実装 )または PBKDF2 ( built-実装中 )。
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());
編集:塩サポートの追加
dtbがコメントで指摘したように、このコードは salt を追加する機能が含まれていればより強力になります。あなたがそれに慣れていないなら、ソルトはハッシュ関数への入力として含まれるランダムなビットのセットであり、ハッシュされたパスワードに対する辞書攻撃を阻止するのに大いに役立ちます(例えば、 Rainbowテーブル )。以下は、saltをサポートするComputeHash
関数の修正バージョンです。
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 saltとハッシュのエンコードについて説明しますが、これは不要であり、不要な計算を追加するだけです。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の速度が最適化されているため、誰かがデータベースにアクセスした場合にパスワードに対するブルートフォース攻撃が容易になることです。
この記事を読む: レインボーテーブルで十分:安全なパスワードスキームについて知っておくべきこと そしてこれ answer 前のSO質問。
記事からの引用:
問題は、MD5が高速であることです。 SHA1やSHA256などの最新の競合製品も同様です。ハッシュはほとんどすべての暗号システムの構成要素であり、通常はパケットごとまたはメッセージごとにデマンド実行されるため、速度は最新の安全なハッシュの設計目標です。
速度は、パスワードハッシュ関数では望ましくないものです。
最後に、パスワードを安全に保存する場合、PHKのMD5スキーム、Provos-MaziereのBcryptスキーム、SRPの3つの合理的なオプションがあることを学びました。正しい選択はBcryptであることがわかりました。
TL; DRは Microsoft.AspNetCore.Cryptography.KeyDerivation を使用し、SH-512でPBKDF2を実装します。
パスワードハッシュを開始するのは、 OWASPガイドライン の内容を確認することです。推奨されるアルゴリズムのリストには、Argon2、PBKDF2、scrypt、およびbcryptが含まれます。これらすべてのアルゴリズムを調整して、パスワードのハッシュにかかる時間と、それに応じてブルートフォースでパスワードを解読する時間を調整できます。これらのアルゴリズムはすべてソルトを使用して、レインボーテーブル攻撃から保護します。
これらのアルゴリズムはどちらもそれほど強力ではありませんが、いくつかの違いがあります。
アルゴリズムのみに基づいて、おそらくbcryptを使用しますが、PBKDF2は最も好ましくありません。
しかし、それは完全な話ではありません。最良のアルゴリズムでさえ、実装が悪いと安全ではなくなる可能性があるからです。 .NETプラットフォームで利用可能なものを見てみましょう。
Rfc2898DeriveBytes
。ここでの最大の利点は、実装がMicrosoftによって提供されることであり、BCrypt.netまたはlibsodium開発者に対するMicrosoft開発者の暗号の勤勉性を適切に評価することはできませんが、.NETアプリケーションを実行している場合、すでにマイクロソフトに大きく依存しています。また、セキュリティの問題が見つかった場合、マイクロソフトが更新プログラムをリリースすることを期待する場合があります。うまくいけば。これまでの研究を要約すると、PBKDF2は4つのアルゴリズムのうち最も優先度が低いかもしれませんが、Microsoftが提供する実装の可用性はそれを上回っているため、合理的な決定はMicrosoft.AspNetCore.Cryptography.KeyDerivation
。
現在、最近のパッケージは.NET Standard 2.0を対象としているため、.NET Core 2.0または.NET Framework 4.6.1以降で使用できます。以前のフレームワークバージョンを使用している場合、.NET Framework 4.5.1または.NET Core 1.0を対象とする以前のバージョンのパッケージ 1.1. を使用できます。残念ながら、以前のバージョンの.NETでも使用することはできません。
ドキュメントと実例は docs.Microsoft.com で入手できます。ただし、そのままコピーアンドペーストしないでください。開発者が行う必要がある決定がまだあります。
最初の決定は、使用するハッシュ関数です。利用可能なオプションには、SHA-1、SHA-256、およびSHA-512があります。そのうち、SHA-1はセキュリティが確保するには速すぎることは間違いありません。SHA-256はまともですが、64ビット操作を使用するとGPUベースの攻撃の恩恵を受けにくくなるため、SHA-512をお勧めします。
次に、パスワードハッシュ出力の長さとソルトの長さを選択する必要があります。ハッシュ関数の出力(SHA-512の場合は512ビットなど)よりも長く出力することは理にかなっておらず、そのようにするのがおそらく最も安全でしょう。塩の長さについては、意見が異なります。 128ビットで十分ですが、いずれにしても、ハッシュ出力の長さよりも長い長さは確かに利点を提供しません。
次に、反復カウントがあります。大きければ大きいほど、パスワードハッシュを解読するのが難しくなりますが、ユーザーのログインに時間がかかります。典型的な運用システムでは、ハッシュが0.25〜1秒かかるように選択することをお勧めします。 10000未満であってはなりません。
通常、バイト配列はソルト値とハッシュ値として取得します。 Base64を使用して文字列に変換します。データベースで2つの異なる列を使用するか、Base64にはないセパレータを使用して1つの列でsaltとパスワードを組み合わせることができます。
将来、より良いハッシュアルゴリズムにシームレスに移行できるように、パスワードハッシュストレージを考案することを忘れないでください。
PBKDF2はHMACSHA1を使用しています.......最新のHMACSHA256またはHMACSHA512の実装が必要であり、さらにキーストレッチングによりアルゴリズムを遅くしたい場合は、このAPIをお勧めします: https://sourceforge.net/projects/pwdtknet /
私は以前と同じ問題を抱えていますが、それを解決できる可能性があるため、これを使用してください
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);
StringBuilder hex = new StringBuilder(hashedBytes.Length * 2);
foreach (byte b in hashedBytes)
hex.AppendFormat("{0:X2}", b);
return hex.ToString();
}
System.Security.Cryptography.SHA256クラスは、トリックを行う必要があります。
http://msdn.Microsoft.com/en-us/library/system.security.cryptography.sha256.aspx