Programming

비밀번호를 해시하는 방법

procodes 2020. 8. 4. 20:15
반응형

비밀번호를 해시하는 방법


전화에 비밀번호 해시를 저장하고 싶지만 어떻게해야할지 모르겠습니다. 암호화 방법 만 찾을 수 있습니다. 비밀번호를 어떻게 올바르게 해시해야합니까?


업데이트 : 이 답변은 상당히 오래되었습니다 . 대신 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);

여기에 나와있는 다른 답변의 대부분은 오늘날의 모범 사례에 비해 다소 오래된 내용입니다. 이와 같이 PBKDF2 / Rfc2898DeriveBytes를 사용하여 비밀번호를 저장하고 확인하는 애플리케이션이 있습니다. 다음 코드는이 게시물의 독립형 클래스입니다 . 소금에 절인 암호 해시를 저장하는 방법의 또 다른 예입니다 . 기본 사항은 정말 간단하므로 여기에서 세분화됩니다.

1 단계 암호화 PRNG로 솔트 값을 만듭니다.

byte[] salt;
new RNGCryptoServiceProvider().GetBytes(salt = new byte[16]);

2 단계 Rfc2898DeriveBytes를 생성하고 해시 값을 얻습니다.

var pbkdf2 = new Rfc2898DeriveBytes(password, salt, 10000);
byte[] hash = pbkdf2.GetBytes(20);

3 단계 나중에 사용할 수 있도록 salt 및 password 바이트를 결합하십시오.

byte[] hashBytes = new byte[36];
Array.Copy(salt, 0, hashBytes, 0, 16);
Array.Copy(hash, 0, hashBytes, 16, 20);

4 단계 결합 된 salt + hash를 문자열로 만들어 보관

string savedPasswordHash = Convert.ToBase64String(hashBytes);
DBContext.AddUser(new User { ..., Password = savedPasswordHash });

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, 10000);
byte[] hash = pbkdf2.GetBytes(20);
/* Compare the results */
for (int i=0; i < 20; i++)
    if (hashBytes[i+16] != hash[i])
        throw new UnauthorizedAccessException();

참고 : 특정 응용 프로그램의 성능 요구 사항에 따라 '10000'값을 줄일 수 있습니다. 최소값은 약 1000이어야합니다.


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 코어 버전도 있습니다 .


암호 암호화에 해시와 솔트를 사용합니다 (Asp.Net 멤버쉽에서 사용하는 것과 동일한 해시).

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

  1. 소금 만들기
  2. salt로 해시 비밀번호를 만듭니다
  3. 해시와 소금 모두 저장
  4. 암호와 소금으로 해독 ... 개발자는 암호를 해독 할 수 없습니다
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); 
      }
 }

I think using KeyDerivation.Pbkdf2 is better than Rfc2898DeriveBytes.

Example and explanation: Hash passwords in 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=
 */

This is a sample code from the article. And it's a minimum security level. To increase it I would use instead of KeyDerivationPrf.HMACSHA1 parameter

KeyDerivationPrf.HMACSHA256 or KeyDerivationPrf.HMACSHA512.

Don't compromise on password hashing. There are many mathematically sound methods to optimize password hash hacking. Consequences could be disastrous. Once a malefactor can get his hands on password hash table of your users it would be relatively easy for him to crack passwords given algorithm is weak or implementation is incorrect. He has a lot of time (time x computer power) to crack passwords. Password hashing should be cryptographically strong to turn "a lot of time" to "unreasonable amount of time".

One more point to add

Hash verification takes time (and it's good). When user enters wrong user name it's takes no time to check that user name is incorrect. When user name is correct we start password verification - it's relatively long process.

For a hacker it would be very easy to understand if user exists or doesn't.

Make sure not to return immediate answer when user name is wrong.

Needless to say : never give an answer what is wrong. Just general "Credentials are wrong".

참고URL : https://stackoverflow.com/questions/4181198/how-to-hash-a-password

반응형