답변:
업데이트 : 이 답변은 매우 오래되었습니다 . 대신 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);
STEP 4 소금 + 해시를 합쳐서 보관 용 끈으로
string savedPasswordHash = Convert.ToBase64String(hashBytes);
DBContext.AddUser(new User { ..., Password = savedPasswordHash });
STEP 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 Membership에서 사용하는 것과 동일한 해시).
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 컴퓨터 성능)이 있습니다. 암호 해싱은 "많은 시간"을 " 불합리한 시간"으로 전환하기 위해 암호 학적으로 강력해야합니다 .
추가 할 점 하나 더
해시 확인에는 시간이 걸립니다 (좋습니다). 사용자가 잘못된 사용자 이름을 입력하면 사용자 이름이 잘못된 지 확인하는 데 시간이 걸리지 않습니다. 사용자 이름이 정확하면 암호 확인을 시작합니다. 이는 비교적 긴 과정입니다.
해커의 경우 사용자가 있는지 여부를 이해하기가 매우 쉽습니다.
사용자 이름이 틀렸을 때 즉시 응답하지 않도록하십시오.
말할 필요도없이, 무엇이 잘못되었는지 절대로 대답하지 마십시오. 일반적인 "자격 증명이 잘못되었습니다".
using
문에 넣거나 호출Clear()
해야합니다.