[C#] C #의 해시 및 솔트 비밀번호

난 그냥에 DavidHayden의 기사 중 하나를 통해가는 해싱 사용자의 암호 .

정말 그가 달성하려는 것을 얻을 수 없습니다.

그의 코드는 다음과 같습니다.

private static string CreateSalt(int size)
{
    //Generate a cryptographic random number.
    RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();
    byte[] buff = new byte[size];
    rng.GetBytes(buff);

    // Return a Base64 string representation of the random number.
    return Convert.ToBase64String(buff);
}

private static string CreatePasswordHash(string pwd, string salt)
{
    string saltAndPwd = String.Concat(pwd, salt);
    string hashedPwd =
        FormsAuthentication.HashPasswordForStoringInConfigFile(
        saltAndPwd, "sha1");
    return hashedPwd;
}

암호를 해싱하고 소금을 추가하는 다른 C # 방법이 있습니까?



답변

실제로 이것은 문자열 변환과 함께 이상합니다. 멤버쉽 공급자가 구성 파일에 넣습니다. 해시 및 솔트는 이진 Blob이므로 텍스트 파일에 저장하지 않는 한 문자열로 변환 할 필요가 없습니다.

내 책, Beginning ASP.NET Security (오, 마지막으로 책을 포주 변명)에서 다음을 수행합니다.

static byte[] GenerateSaltedHash(byte[] plainText, byte[] salt)
{
  HashAlgorithm algorithm = new SHA256Managed();

  byte[] plainTextWithSaltBytes =
    new byte[plainText.Length + salt.Length];

  for (int i = 0; i < plainText.Length; i++)
  {
    plainTextWithSaltBytes[i] = plainText[i];
  }
  for (int i = 0; i < salt.Length; i++)
  {
    plainTextWithSaltBytes[plainText.Length + i] = salt[i];
  }

  return algorithm.ComputeHash(plainTextWithSaltBytes);
}

소금 생성은 문제의 예와 같습니다. 을 사용하여 텍스트를 바이트 배열로 변환 할 수 있습니다 Encoding.UTF8.GetBytes(string). 당신은 그것의 문자열 표현으로 해시를 변환해야하는 경우 사용할 수있는 Convert.ToBase64StringConvert.FromBase64String 그것을 다시 변환 할 수 있습니다.

바이트 배열에는 항등 연산자를 사용할 수 없으며 참조를 확인하므로 두 배열을 반복하여 각 바이트를 검사해야합니다.

public static bool CompareByteArrays(byte[] array1, byte[] array2)
{
  if (array1.Length != array2.Length)
  {
    return false;
  }

  for (int i = 0; i < array1.Length; i++)
  {
    if (array1[i] != array2[i])
    {
      return false;
    }
  }

  return true;
}

비밀번호마다 항상 새 소금을 사용하십시오. 소금은 비밀로 유지 될 필요가 없으며 해시 자체와 함께 저장 될 수 있습니다.


답변

blowdart가 말한 것은 있지만 코드가 약간 적습니다. Linq를 사용하거나 CopyTo배열을 연결 하십시오 .

public static byte[] Hash(string value, byte[] salt)
{
    return Hash(Encoding.UTF8.GetBytes(value), salt);
}

public static byte[] Hash(byte[] value, byte[] salt)
{
    byte[] saltedValue = value.Concat(salt).ToArray();
    // Alternatively use CopyTo.
    //var saltedValue = new byte[value.Length + salt.Length];
    //value.CopyTo(saltedValue, 0);
    //salt.CopyTo(saltedValue, value.Length);

    return new SHA256Managed().ComputeHash(saltedValue);
}

Linq는 바이트 배열을 쉽게 비교할 수있는 방법을 제공합니다.

public bool ConfirmPassword(string password)
{
    byte[] passwordHash = Hash(password, _passwordSalt);

    return _passwordHash.SequenceEqual(passwordHash);
}

그러나이 중 하나를 구현하기 전에이 게시물을 확인하십시오. . 비밀번호 해싱의 경우 빠른 알고리즘이 아닌 느린 해시 알고리즘을 원할 수 있습니다.

이를 위해 Rfc2898DeriveBytes느리고 (느려질 수있는) 클래스가 있으며, 비밀번호와 소금을 가져와 해시를 반환 할 수 있다는 점에서 원래 질문의 두 번째 부분에 대답 할 수 있습니다. 자세한 내용은 이 질문 을 참조하십시오. 참고 스택 교환 사용Rfc2898DeriveBytes (소스 코드를 암호 해시를 위해 여기 ).


답변

SHA256과 같은 해싱 함수는 실제로 암호 저장에 사용하기위한 것이 아니라는 것을 읽었습니다.
https://patrickmn.com/security/storing-passwords-securely/#notpasswordhashes

대신 PBKDF2, bcrypt 또는 scrypt와 같은 적응 형 키 파생 함수가있었습니다. 다음은 Microsoft가 Microsoft.AspNet.Identity 라이브러리에서 PasswordHasher 에 대해 작성한 PBKDF2 기반 제품입니다 .

/* =======================
 * HASHED PASSWORD FORMATS
 * =======================
 *
 * Version 3:
 * PBKDF2 with HMAC-SHA256, 128-bit salt, 256-bit subkey, 10000 iterations.
 * Format: { 0x01, prf (UInt32), iter count (UInt32), salt length (UInt32), salt, subkey }
 * (All UInt32s are stored big-endian.)
 */

public string HashPassword(string password)
{
    var prf = KeyDerivationPrf.HMACSHA256;
    var rng = RandomNumberGenerator.Create();
    const int iterCount = 10000;
    const int saltSize = 128 / 8;
    const int numBytesRequested = 256 / 8;

    // Produce a version 3 (see comment above) text hash.
    var salt = new byte[saltSize];
    rng.GetBytes(salt);
    var subkey = KeyDerivation.Pbkdf2(password, salt, prf, iterCount, numBytesRequested);

    var outputBytes = new byte[13 + salt.Length + subkey.Length];
    outputBytes[0] = 0x01; // format marker
    WriteNetworkByteOrder(outputBytes, 1, (uint)prf);
    WriteNetworkByteOrder(outputBytes, 5, iterCount);
    WriteNetworkByteOrder(outputBytes, 9, saltSize);
    Buffer.BlockCopy(salt, 0, outputBytes, 13, salt.Length);
    Buffer.BlockCopy(subkey, 0, outputBytes, 13 + saltSize, subkey.Length);
    return Convert.ToBase64String(outputBytes);
}

public bool VerifyHashedPassword(string hashedPassword, string providedPassword)
{
    var decodedHashedPassword = Convert.FromBase64String(hashedPassword);

    // Wrong version
    if (decodedHashedPassword[0] != 0x01)
        return false;

    // Read header information
    var prf = (KeyDerivationPrf)ReadNetworkByteOrder(decodedHashedPassword, 1);
    var iterCount = (int)ReadNetworkByteOrder(decodedHashedPassword, 5);
    var saltLength = (int)ReadNetworkByteOrder(decodedHashedPassword, 9);

    // Read the salt: must be >= 128 bits
    if (saltLength < 128 / 8)
    {
        return false;
    }
    var salt = new byte[saltLength];
    Buffer.BlockCopy(decodedHashedPassword, 13, salt, 0, salt.Length);

    // Read the subkey (the rest of the payload): must be >= 128 bits
    var subkeyLength = decodedHashedPassword.Length - 13 - salt.Length;
    if (subkeyLength < 128 / 8)
    {
        return false;
    }
    var expectedSubkey = new byte[subkeyLength];
    Buffer.BlockCopy(decodedHashedPassword, 13 + salt.Length, expectedSubkey, 0, expectedSubkey.Length);

    // Hash the incoming password and verify it
    var actualSubkey = KeyDerivation.Pbkdf2(providedPassword, salt, prf, iterCount, subkeyLength);
    return actualSubkey.SequenceEqual(expectedSubkey);
}

private static void WriteNetworkByteOrder(byte[] buffer, int offset, uint value)
{
    buffer[offset + 0] = (byte)(value >> 24);
    buffer[offset + 1] = (byte)(value >> 16);
    buffer[offset + 2] = (byte)(value >> 8);
    buffer[offset + 3] = (byte)(value >> 0);
}

private static uint ReadNetworkByteOrder(byte[] buffer, int offset)
{
    return ((uint)(buffer[offset + 0]) << 24)
        | ((uint)(buffer[offset + 1]) << 16)
        | ((uint)(buffer[offset + 2]) << 8)
        | ((uint)(buffer[offset + 3]));
}

.NET Standard 2.0 (.NET 4.6.1 이상) 이 필요한 Microsoft.AspNetCore.Cryptography.KeyDerivation nuget 패키지가 설치되어 있어야합니다. 이전 버전의 .NET은 암호화를 참조하십시오. Microsoft의 System.Web.Helpers 라이브러리에서 클래스를 .

2015 년 11 월
업데이트 PBKDF2-HMAC-SHA1 대신 PBKDF2-HMAC-SHA256 해싱을 사용하는 다른 Microsoft 라이브러리의 구현을 사용하도록 업데이트되었습니다 ( IterCount가 충분히 높은 경우 PBKDF2-HMAC-SHA1은 여전히 안전 합니다). 단순화 된 코드가 복사 된 소스를 확인할 수 있습니다. 이전 답변에서 구현 된 해시의 유효성 검사 및 업그레이드를 실제로 처리하므로 나중에 iterCount를 늘려야하는 경우 유용합니다.


답변

Salt는 해시에 복잡성을 추가하여 무차별 대입 균열을 어렵게 만듭니다.

Sitepoint기사에서 :

해커는 여전히 사전 공격을 수행 할 수 있습니다. 예를 들어, 악의적 인 사용자는 사람들이 자주 사용하는 암호 (예 : 도시 이름, 스포츠 팀 등)를 100,000 개 암호를 사용하여 해시 한 다음 사전의 각 항목을 데이터베이스의 각 행과 비교하여 사전 공격을 수행 할 수 있습니다. 표. 해커가 일치하면 빙고! 그들은 당신의 암호를 가지고 있습니다. 그러나이 문제를 해결하려면 해시에만 소금을 바르십시오.

해시를 소금에 담기 위해 무작위로 보이는 텍스트 문자열을 만들어 사용자가 제공 한 암호로 연결 한 다음 무작위로 생성 된 문자열과 암호를 하나의 값으로 함께 해시합니다. 그런 다음 해시와 솔트를 모두 Users 테이블에 별도의 필드로 저장합니다.

이 시나리오에서 해커는 암호를 추측해야 할뿐만 아니라 소금도 추측해야합니다. 일반 텍스트에 솔트를 추가하면 보안이 향상됩니다. 이제 해커가 사전 공격을 시도하면 모든 사용자 행의 솔트로 100,000 개의 항목을 해시해야합니다. 여전히 가능하지만 해킹 성공 가능성은 급격히 감소합니다.

.NET에는 자동 으로이 작업을 수행하는 방법이 없으므로 위의 솔루션을 사용하십시오.


답변

다음과 같은 방법으로 클래스를 만들었습니다.

  1. 소금 만들기
  2. 해시 입력
  3. 입력 확인

    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);
          }
     }

    `


답변

바,이게 낫다! http://sourceforge.net/projects/pwdtknet/ 그리고 ….. 키 스트레칭을 수행하고 HMACSHA512를 사용 하기 때문에 더 좋습니다. 🙂


답변

Microsoft에서 제공하는 기본 클래스를 사용하여 해싱 프로세스를 쉽게 수행 할 수 있도록 라이브러리 SimpleHashing.Net 을 만들었습니다. 일반 SHA로는 더 이상 암호를 안전하게 저장하기에 충분하지 않습니다.

라이브러리는 Bcrypt의 해시 형식이라는 아이디어를 사용하지만 공식 MS 구현이 없기 때문에 프레임 워크 (예 : PBKDF2)에서 사용할 수있는 것을 사용하는 것을 선호하지만 상자에서 너무 어렵습니다.

다음은 라이브러리 사용 방법에 대한 간단한 예입니다.

ISimpleHash simpleHash = new SimpleHash();

// Creating a user hash, hashedPassword can be stored in a database
// hashedPassword contains the number of iterations and salt inside it similar to bcrypt format
string hashedPassword = simpleHash.Compute("Password123");

// Validating user's password by first loading it from database by username
string storedHash = _repository.GetUserPasswordHash(username);
isPasswordValid = simpleHash.Verify("Password123", storedHash);