데이터 보호 코드 베이스에는 암호화 키 파생 함수를 포함하는Microsoft.AspNetCore.Cryptography.KeyDerivation패키지가 포함되어 있습니다. 이 패키지는 독립 실행형 구성 요소이며 나머지 데이터 보호 시스템에 대한 종속성이 없습니다. 완전히 독립적으로 사용할 수 있습니다. 원본은 데이터 보호 코드 베이스와 함께 편의를 위해 존재합니다.
패키지는 현재PBKDF2 알고리즘을 사용하여 암호를 해시할 수 있는KeyDerivation.Pbkdf2메서드를 제공합니다. 이 API는 .NET Framework 기존Rfc2898DeriveBytesAPI와 매우 유사하지만 세 가지 중요한 차이점이 있습니다.
KeyDerivation.Pbkdf2메서드는 여러 PRF(현재HMACSHA1, HMACSHA256 및 HMACSHA512)를 사용하도록 지원하지만Rfc2898DeriveBytes형식은HMACSHA1을 지원합니다.
KeyDerivation.Pbkdf2메서드는 현재 운영 체제를 검색하고 가장 최적화된 루틴 구현을 선택하려고 시도하여 특정 경우에 훨씬 더 나은 성능을 제공합니다. (Windows 8에서Rfc2898DeriveBytes처리량의 10배를 제공합니다.)
KeyDerivation.Pbkdf2메서드에는 호출자가 모든 매개 변수(salt, PRF 및 반복 횟수)를 지정해야 합니다.Rfc2898DeriveBytes형식은 이에 대한 기본값을 제공합니다.
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 cryptographically strong random sequence of nonzero values
byte[] salt = new byte[128 / 8];
using (var rngCsp = new RNGCryptoServiceProvider())
{
rngCsp.GetNonZeroBytes(salt);
}
Console.WriteLine($"Salt: {Convert.ToBase64String(salt)}");
// derive a 256-bit subkey (use HMACSHA256 with 100,000 iterations)
string hashed = Convert.ToBase64String(KeyDerivation.Pbkdf2(
password: password,
salt: salt,
prf: KeyDerivationPrf.HMACSHA256,
iterationCount: 100000,
numBytesRequested: 256 / 8));
Console.WriteLine($"Hashed: {hashed}");
}
}
/*
* SAMPLE OUTPUT
*
* Enter a password: Xtw9NMgx
* Salt: CGYzqeN4plZekNC88Umm1Q==
* Hashed: Gt9Yc4AiIvmsC1QQbe2RZsCIqvoYlst2xbz0Fs8aHnw=
*/
Asp.net Core 에서 오직 암호화 및 비교만 심플하게 사용할 경우 Helper 함수 만들어 활용해 보겠습니다.
이를 구현하기 위해서는암호화 키 파생 함수를 포함하는Microsoft.AspNetCore.Cryptography.KeyDerivation와System.Security.Cryptography클래스를 사용해야 합니다.
먼저 HMACSHA512으로 하였으며, 회전 횟수는 10,000 번이고, Salt 는 랜덤 생성하여 이를 조합해서 비밀번호 암호화를 하였고, 암호 값은 salt.hash 형태의 값을 반환받게 하였습니다. 구분을 위해 . (포인트) 을 사용하였습니다.
비밀번호 맞는지 여부의 확인은 비밀번호 암호화 값에서 salt 와 hash 값을 분리하여 이를 다시 검증하는 방법으로 구현 되어 있습니다.
using Microsoft.AspNetCore.Cryptography.KeyDerivation;
using System;
using System.Security.Cryptography;
using System.Text;
namespace WebApplication3
{
public class PasswordHasher
{
private static string HashPassword(string value, string salt)
{
var valueBytes = KeyDerivation.Pbkdf2(
password: value,
salt: Encoding.UTF8.GetBytes(salt),
prf: KeyDerivationPrf.HMACSHA512,
iterationCount: 10000,
numBytesRequested: 256 / 8);
return Convert.ToBase64String(valueBytes);
}
public static string HashPassword(string password)
{
var salt = GenerateSalt();
var hash = HashPassword(password, salt);
var result = $"{salt}.{hash}";
Console.WriteLine("hash result:{0}", result);
return result;
}
private static bool Validate(string password, string salt, string hash) => HashPassword(password, salt) == hash;
public static bool VerifyHashedPassword(string password, string storePassword)
{
if (string.IsNullOrEmpty(password))
{
throw new ArgumentNullException(nameof(password));
}
if (string.IsNullOrEmpty(storePassword))
{
throw new ArgumentNullException(nameof(storePassword));
}
var parts = storePassword.Split('.');
var salt = parts[0];
var hash = parts[1];
return Validate(password, salt, hash); ;
}
private static string GenerateSalt()
{
byte[] randomBytes = new byte[128 / 8];
using (var generator = RandomNumberGenerator.Create())
{
generator.GetBytes(randomBytes);
return Convert.ToBase64String(randomBytes);
}
}
}
}
Asp.net Core 의 Controller 에서 구현하기
Controller 에서 이를 테스트 해봤습니다. 비밀번호 값을 5번 정도 작동 해봤으며 전부 다른 형태의 비밀번호 암호화 값을 제공해 주었습니다. result 값은 database 의 사용자 테이블에 저장해 두고 나서, 인증 할 때 이를 꺼내서 비교하는 방법을 구현하면 되겠습니다.
public IActionResult Index()
{
var password = "Aspdotnet.tistory.com";
for(int i = 0; i < 5; i++)
{
var result = PasswordHasher.HashPassword(password);
var right = PasswordHasher.VerifyHashedPassword(password, result);
Console.WriteLine("password is right : {0}", right);
}
return View();
}