Şifre nasıl hash edilir?


117

Bir şifrenin karmasını telefonda saklamak istiyorum, ancak nasıl yapacağımı bilmiyorum. Sadece şifreleme yöntemlerini bulabiliyorum. Parola nasıl düzgün bir şekilde hashing edilmelidir?

Yanıtlar:


62

GÜNCELLEME : BU CEVAP CİDDİ ŞEKİLDE GEÇMİŞTİR . Lütfen bunun yerine https://stackoverflow.com/a/10402129/251311'deki önerileri kullanın.

Ya kullanabilirsiniz

var md5 = new MD5CryptoServiceProvider();
var md5data = md5.ComputeHash(data);

veya

var sha1 = new SHA1CryptoServiceProvider();
var sha1data = sha1.ComputeHash(data);

dataBayt dizisi olarak almak için kullanabilirsiniz

var data = Encoding.ASCII.GetBytes(password);

ve geri dize almak md5dataveyasha1data

var hashedPassword = ASCIIEncoding.GetString(md5data);

11
SHA1'i GERÇEKTEN tavsiye ederim. Mevcut bir sistemle geriye dönük uyumluluğu sürdürmediğiniz sürece MD5 hayır-hayırdır. Ek olarak, uygulamayı kullanmayı bitirdiğinizde bunu bir usingifadeye koyduğunuzdan veya çağırdığınızdan emin olun Clear().
vcsjones

3
@vcsjones: Burada kutsal savaş istemiyorum, ancak md5neredeyse her tür görev için yeterince iyi. Güvenlik açıkları aynı zamanda çok özel durumları da ifade eder ve neredeyse saldırganın kriptografi hakkında çok şey bilmesini gerektirir.
zerkms

4
@zerkms noktası alındı, ancak geriye dönük uyumluluk için bir neden yoksa, MD5 kullanmak için bir neden yok. "Eşeği sağlam kazığa bağlamak".
vcsjones

4
Bu noktada MD5 kullanmak için bir neden yok. Hesaplama süresinin önemsiz olduğu düşünüldüğünde, mevcut sistemlerle uyumluluk dışında MD5 kullanmak için bir neden yoktur. MD5 "yeterince iyi" olsa bile, kullanıcı için çok daha güvenli SHA için hiçbir maliyet yoktur. Eminim zerkms, yorumun sorgulayıcı için daha fazla olduğunu biliyor.
Gerald Davis

11
Üç büyük hata: 1) ASCII, sıradışı karakterler içeren parolaları sessizce düşürür 2) Düz MD5 / SHA-1 / SHA-2 hızlıdır. 3) Bir tuza ihtiyacınız var. | Bunun yerine PBKDF2, bcrypt veya scrypt kullanın. PBKDF2, Rfc2898DeriveBytes sınıfında en kolayıdır (WP7'de mevcut olup olmadığından emin değil)
CodesInChaos

300

Buradaki diğer cevapların çoğu, günümüzün en iyi uygulamaları ile biraz güncel değil. Bu nedenle, Rfc2898DeriveBytesşifreleri saklamak ve doğrulamak için PBKDF2 / kullanma uygulamasıdır . Aşağıdaki kod, bu gönderide bağımsız bir sınıftadır: Tuzlu bir şifre karmasının nasıl saklanacağına dair başka bir örnek . Temel bilgiler gerçekten çok kolay, bu yüzden burada ayrıntılı olarak açıklanmıştır:

ADIM 1 Kriptografik bir PRNG ile tuz değerini oluşturun:

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

ADIM 2 Rfc2898DeriveBytes'i oluşturun ve hash değerini alın:

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

ADIM 3 Daha sonra kullanmak için tuz ve şifre baytlarını birleştirin:

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

ADIM 4 Birleşik tuz + hash'i depolama için bir dizeye çevirin

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

ADIM 5 Kullanıcı tarafından girilen parolayı kayıtlı bir parolayla karşılaştırarak doğrulayın

/* 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();

Not: Özel uygulamanızın performans gereksinimlerine bağlı olarak değer 100000düşürülebilir. Minimum değer civarında olmalıdır 10000.


8
@Daniel temelde gönderi, tek başına hash'den daha güvenli bir şey kullanmakla ilgili. Sadece bir şifreyi karıştırırsanız, salt ile bile, kullanıcı şifreleri siz onlara değiştirmelerini söyleme şansınız bile olmadan tehlikeye atılır (ve muhtemelen satılır / yayınlanır). Yukarıdaki kodu, saldırgan için zorlaştırmak için kullanın, geliştirici için kolay değil.
csharptest.net

2
@DatVM Hayır, bir hash depoladığınız her sefer için yeni tuz. bu nedenle, parolayı doğrulayabilmeniz için depolama için karma ile birleştirilir.
csharptest.net

9
@CiprianJijie tüm mesele, yapamayacağınızı varsaymaktır.
csharptest.net

9
Birinin VerifyPassword yöntemini kullanması durumunda, Linq'i ve bir boole için daha kısa bir çağrıyı kullanmak istiyorsanız, bu şunu yapacaktır: hash.SequenceEqual (hashBytes.Skip (_saltSize));
Jesú Castillo

2
@ csharptest.net Ne tür dizi boyutları önerirsiniz? dizinin boyutu güvenliği çok etkiliyor mu?
Hashing

72

Csharptest.net'in harika cevabına dayanarak, bunun için bir Sınıf yazdım:

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

Kullanımı:

// Hash
var hash = SecurePasswordHasher.Hash("mypassword");

// Verify
var result = SecurePasswordHasher.Verify("mypassword", hash);

Örnek bir karma şu olabilir:

$MYHASH$V1$10000$Qhxzi6GNu/Lpy3iUqkeqR/J1hh8y/h5KPDjrv89KzfCVrubn

Gördüğünüz gibi, kolay kullanım için iterasyonları ve yükseltmemiz gerekirse bunu yükseltme olasılığını da ekledim.


.Net çekirdeği ile ilgileniyorsanız, Kod İncelemesinde bir .net çekirdek sürümüm de var .


1
Doğrulamak için, hashing motorunu yükseltirseniz, hash'inizin V1 bölümünü artırıp anahtarını kapatır mısınız?
Mike Cole

1
Evet plan bu. Daha sonra dayanan karar vereceğini V1ve V2hangi doğrulama ihtiyacınız yöntemi.
Christian Gollhardt

Cevabınız ve dersiniz için teşekkürler. Biz konuşurken onu uyguluyorum.
Mike Cole

2
Evet @NelsonSilva. Tuz yüzünden .
Christian Gollhardt

1
Bu kodun tüm kopyalanması / yapıştırılmasıyla (ben dahil), umarım birisi konuşur ve onunla ilgili bir sorun bulunursa gönderi revize edilir! :)
pettys

14

Parola şifrelemem için bir karma ve bir tuz kullanıyorum (Asp.Net Üyeliğinin kullandığı karmayla aynıdır):

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

16
Hızlı olan düz SHA-1 kullanımı için -1. PBKDF2, bcrypt veya scrypt gibi yavaş bir anahtar türetme işlevi kullanın.
CodesInChaos

2
  1. Bir tuz oluşturun,
  2. Salt ile bir karma şifre oluşturun
  3. Hem karmayı hem de tuzu saklayın
  4. şifre ve tuz ile şifresini çöz ... böylece geliştiriciler şifrenin şifresini çözemez
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); 
      }
 }

1

KeyDerivation.Pbkdf2 kullanmanın Rfc2898DeriveBytes'ten daha iyi olduğunu düşünüyorum.

Örnek ve açıklama: ASP.NET Core'da karma parolalar

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=
 */

Bu, makaleden örnek bir koddur. Ve minimum güvenlik seviyesidir. Arttırmak için KeyDerivationPrf yerine kullanırdım.HMACSHA1 parametresi

KeyDerivationPrf.HMACSHA256 veya KeyDerivationPrf.HMACSHA512.

Parola karmaşasından ödün vermeyin. Parola hash hacklemeyi optimize etmek için matematiksel olarak sağlam birçok yöntem vardır. Sonuçlar felaket olabilir. Bir kötü niyetli kişi, kullanıcılarınızın şifre karma tablosunu ele geçirdiğinde, algoritmanın zayıf olması veya uygulamanın yanlış olması nedeniyle şifreleri kırması nispeten daha kolay olacaktır. Şifreleri kırmak için çok zamanı (zaman x bilgisayar gücü) var. Parola hashingi, "çok fazla zamanı" " mantıksız bir süreye" dönüştürmek için kriptografik olarak güçlü olmalıdır .

Eklenecek bir nokta daha

Karma doğrulama zaman alır (ve iyidir). Kullanıcı yanlış kullanıcı adı girdiğinde, kullanıcı adının yanlış olup olmadığını kontrol etmek hiç zaman almaz. Kullanıcı adı doğru olduğunda parola doğrulamasını başlatırız - bu nispeten uzun bir süreçtir.

Bir bilgisayar korsanı için, kullanıcının var olup olmadığını anlamak çok kolay olacaktır.

Kullanıcı adı yanlış olduğunda hemen cevap vermemeye dikkat edin.

Söylemeye gerek yok: neyin yanlış olduğuna asla cevap vermeyin. Sadece genel "Kimlik bilgileri yanlış".


1
BTW, önceki yanıt stackoverflow.com/a/57508528/11603057 doğru ve zararlı değildir. Bu, parola karması değil, karma bir örnektir. Anahtar türetme işlemi sırasında sözde rasgele işlevin yinelemeleri olmalıdır. Yok. Yorum yapamam veya olumsuz oy veremiyorum (düşük itibarım). Lütfen yanlış cevapları kaçırmayın!
Albert Lyubarsky

1

@ csharptest.net ve Christian Gollhardt'ın cevapları harika, çok teşekkür ederim. Ancak bu kodu milyonlarca kayıtla üretimde çalıştırdıktan sonra bir bellek sızıntısı olduğunu keşfettim. RNGCryptoServiceProvider ve Rfc2898DeriveBytes sınıfları IDisposable'dan türetilir, ancak bunları elden çıkarmayız. Birinin elden çıkarılmış versiyona ihtiyacı olursa çözümümü bir cevap olarak yazacağım.

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

    }
}

Kullanımı:

// Hash
var hash = SecurePasswordHasher.Hash("mypassword");

// Verify
var result = SecurePasswordHasher.Verify("mypassword", hash);

0

Önce Tuz Üretmek için aşağıdaki sınıfı kullanın. Her kullanıcının farklı bir tuza sahip olması gerekir, onu diğer kullanıcı özellikleriyle birlikte veritabanına kaydedebiliriz. Tur değeri, şifrenin kaç kez karma haline getirileceğine karar verir.

Daha fazla ayrıntı için: https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.rfc2898derivebytes.-ctor?view=netcore-3.1#System_Security_Cryptography_Rfc2898DeriveBytes__ctor_System_Byte___System_Byte___System_Int32 _

public class HashSaltWithRounds
{
    int saltLength = 32;
    public byte[] GenerateSalt()
    {
        using (var randomNumberGenerator = new RNGCryptoServiceProvider())
        {
            var randomNumber = new byte[saltLength];
            randomNumberGenerator.GetBytes(randomNumber);
            return randomNumber;
        }
    }

    public string HashDataWithRounds(byte[] password, byte[] salt, int rounds)
    {
        using(var rfc2898= new Rfc2898DeriveBytes(password, salt, rounds))
        {
            return Convert.ToBase64String(rfc2898.GetBytes(32));
        }
    }
}

Aşağıdaki gibi bir konsol uygulamasından çağırabiliriz. Aynı tuzu kullanarak şifreyi iki kez hashledim .

public class Program
{
    public static void Main(string[] args)
    {
        int numberOfIterations = 99;
        var hashFunction = new HashSaltWithRounds();

        string password = "Your Password Here";
        byte[] salt = hashFunction.GenerateSalt();

        var hashedPassword1 = hashFunction.HashDataWithRounds(Encoding.UTF8.GetBytes(password), salt, numberOfIterations);
        var hashedPassword2 = hashFunction.HashDataWithRounds(Encoding.UTF8.GetBytes(password), salt, numberOfIterations);

        Console.WriteLine($"hashedPassword1 :{hashedPassword1}");
        Console.WriteLine($"hashedPassword2 :{hashedPassword2}");
        Console.WriteLine(hashedPassword1.Equals(hashedPassword2));

        Console.ReadLine();

    }
}

Çıktı

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.