ASP.NET Identity's Default Password Hasher - Nasıl çalışır ve güvenli mi?


162

MVC 5 ve ASP.NET Identity Framework ile birlikte gelen UserManager varsayılan uygulanan Parola Hasher yeterince güvenli olup olmadığını merak ediyorum ? Ve eğer öyleyse, bana nasıl çalıştığını açıklayabilirsen?

IPasswordHasher arayüzü şöyle görünür:

public interface IPasswordHasher
{
    string HashPassword(string password);
    PasswordVerificationResult VerifyHashedPassword(string hashedPassword, 
                                                       string providedPassword);
}

Gördüğünüz gibi, bir tuz almaz, ancak bu konuda bahsedilmiştir: " Asp.net Identity password hashing " perde arkasına tuz bulaşır. Bunu nasıl yaptığını merak ediyorum. Ve bu tuz nereden geliyor?

Benim endişem tuzun statik olması ve güvensiz olması.


Bunun doğrudan sorunuzu cevapladığını düşünmüyorum, ancak Brock Allen bazı endişelerinizi burada yazdı => brockallen.com/2013/10/20/… ve ayrıca çeşitli açık kaynak kodlu bir kullanıcı kimliği yönetimi ve kimlik doğrulama kütüphanesi yazdı şifre sıfırlama, hashing vb. gibi kazan plakası özellikleri github.com/brockallen/BrockAllen.MembershipReboot
Shiva

@Shiva Teşekkürler, sayfadaki kütüphaneye ve videoya bakacağım. Fakat harici bir kütüphaneyle uğraşmak istemem. Bunu önleyebilirsem olmaz.
André Snede Kock

2
FYI: güvenlik için yığın akışı eşdeğeri. Bu yüzden sık sık iyi / doğru bir cevap alacaksınız. Uzmanlar security.stackexchange.com üzerinde özellikle "güvenli mi" yorumu benzer bir soru sordum ve cevap derinliği ve kalitesi şaşırtıcıydı.
phil soady

@philsoady Teşekkürler, bu elbette mantıklı, zaten diğer "alt forumlardan" birkaçındayım, bir cevap alamazsam, kullanabilirim, devam edeceğim securiry.stackexchange.com. Ve bahşiş için teşekkürler!
André Snede Kock

Yanıtlar:


227

Varsayılan uygulamanın ( ASP.NET Framework veya ASP.NET Core ) nasıl çalıştığı aşağıda açıklanmıştır. Karma üretmek için rastgele tuzlu bir Anahtar Türev Fonksiyonu kullanır . Tuz, KDF'nin çıktısının bir parçası olarak dahil edilir. Böylece, her zaman aynı şifreyi "hash" farklı karma alırsınız. Karmayı doğrulamak için çıktı tuz ve geri kalanına geri bölünür ve KDF şifre üzerinde belirtilen tuzla tekrar çalıştırılır. Sonuç ilk çıktının geri kalanıyla eşleşirse, karma doğrulanır.

Karma:

public static string HashPassword(string password)
{
    byte[] salt;
    byte[] buffer2;
    if (password == null)
    {
        throw new ArgumentNullException("password");
    }
    using (Rfc2898DeriveBytes bytes = new Rfc2898DeriveBytes(password, 0x10, 0x3e8))
    {
        salt = bytes.Salt;
        buffer2 = bytes.GetBytes(0x20);
    }
    byte[] dst = new byte[0x31];
    Buffer.BlockCopy(salt, 0, dst, 1, 0x10);
    Buffer.BlockCopy(buffer2, 0, dst, 0x11, 0x20);
    return Convert.ToBase64String(dst);
}

Doğrulanıyor:

public static bool VerifyHashedPassword(string hashedPassword, string password)
{
    byte[] buffer4;
    if (hashedPassword == null)
    {
        return false;
    }
    if (password == null)
    {
        throw new ArgumentNullException("password");
    }
    byte[] src = Convert.FromBase64String(hashedPassword);
    if ((src.Length != 0x31) || (src[0] != 0))
    {
        return false;
    }
    byte[] dst = new byte[0x10];
    Buffer.BlockCopy(src, 1, dst, 0, 0x10);
    byte[] buffer3 = new byte[0x20];
    Buffer.BlockCopy(src, 0x11, buffer3, 0, 0x20);
    using (Rfc2898DeriveBytes bytes = new Rfc2898DeriveBytes(password, dst, 0x3e8))
    {
        buffer4 = bytes.GetBytes(0x20);
    }
    return ByteArraysEqual(buffer3, buffer4);
}

7
Bunu doğru anlarsam, HashPasswordişlev, her ikisini de aynı dizede döndürür? Ve doğruladığınızda, tekrar tekrar böler ve gelen açık metin parolasını, bölünmüş tuz ile karıştırır ve orijinal karma ile karşılaştırır?
André Snede Kock

9
@ AndréSnedeHansen, kesinlikle. Ben de size güvenlik veya kriptografi SE sormanızı tavsiye ederim. "Güvenli midir" kısmı ilgili bağlamlarda daha iyi ele alınabilir.
Andrew Savinykh

1
@shajeerpuzhakkal yukarıdaki cevapta tarif edildiği gibi.
Andrew Savinykh

3
@AndrewSavinykh Biliyorum, bu yüzden soruyorum - amaç nedir? Kodun daha akıllı görünmesini sağlamak için? ;) Çünkü ondalık sayılar kullanarak bir şeyleri saymak benim için çok daha sezgisel (sonuçta 10 parmağımız var - en azından çoğumuz), bu yüzden onaltılı sayılar kullanarak bir şey bildirmek gereksiz bir kod gizleme gibi görünüyor.
Andrew Cyrul

1
@ MihaiAlexandru-Ionut var hashedPassword = HashPassword(password); var result = VerifyHashedPassword(hashedPassword, password);- yapmanız gereken bu. bundan sonra resultdoğru içerir.
Andrew Savinykh

43

ASP.NET bu günlerde açık kaynak olduğundan, GitHub'da bulabilirsiniz: AspNet.Identity 3.0 ve AspNet.Identity 2.0 .

Yorumlardan:

/* =======================
 * HASHED PASSWORD FORMATS
 * =======================
 * 
 * Version 2:
 * PBKDF2 with HMAC-SHA1, 128-bit salt, 256-bit subkey, 1000 iterations.
 * (See also: SDL crypto guidelines v5.1, Part III)
 * Format: { 0x00, salt, subkey }
 *
 * 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.)
 */

Evet ve dikkat çekmeye değer, zespri'nin gösterdiği algoritmaya eklemeler var.
André Snede Kock

1
GitHub'daki kaynak, halen yayınlanmakta olan Asp.Net.Identity 3.0'dır. 2.0 hash fonksiyonunun kaynağı CodePlex'te
David

1
En yeni uygulama şimdi github.com/dotnet/aspnetcore/blob/master/src/Identity/… altında bulunabilir . Diğer havuzu arşivlediler;)
FranzHuber23

32

Kabul edilen cevabı anladım ve oy verdim ama yandaşlarımın cevabını buraya dökeceğimizi düşündüm ...

Karma oluşturma

  1. Tuz, bir karma ve bir tuz üreten Rfc2898DeriveBytes fonksiyonu kullanılarak rastgele üretilir . Rfc2898DeriveBytes girişleri parola, üretilecek tuzun boyutu ve gerçekleştirilecek karma yineleme sayısıdır. https://msdn.microsoft.com/en-us/library/h83s4e12(v=vs.110).aspx
  2. Tuz ve karma daha sonra birlikte ezilir (önce tuz ardından karma) ve bir ip olarak kodlanır (böylece tuz karma içinde kodlanır). Bu kodlanmış karma (tuz ve karma içeren) daha sonra (tipik olarak) kullanıcıya karşı veritabanında saklanır.

Bir karma için parola kontrol etme

Kullanıcının girdiği şifreyi kontrol etmek için.

  1. Tuz, saklanan karma paroladan çıkarılır.
  2. Tuz, kullanıcı giriş şifresini, Rfc2898DeriveBytes aşırı yüklemesi kullanarak bir tane üretmek yerine bir tuz alırken hash etmek için kullanılır . https://msdn.microsoft.com/en-us/library/yx129kfs(v=vs.110).aspx
  3. Saklanan karma ve test karma karşılaştırılır.

Karma

Kapakların altında karma, SHA1 karma işlevi kullanılarak oluşturulur ( https://en.wikipedia.org/wiki/SHA-1 ). Bu işlev yinelenen şekilde 1000 kez çağrılır (Varsayılan Kimlik uygulamasında)

Bu neden güvenli

  • Rastgele tuzlar, bir saldırganın şifreleri denemek ve kırmak için önceden oluşturulmuş bir karma tablo kullanamayacağı anlamına gelir. Her tuz için bir karma tablosu oluşturmaları gerekir. (Burada bilgisayar korsanının tuzunuzu tehlikeye attığını varsayarsak)
  • 2 parola aynı ise farklı karma değerlere sahip olurlar. (yani saldırganlar 'ortak' şifreleri çıkaramazlar)
  • SHA1'i 1000 kez yinelemek, saldırganın da bunu yapması gerektiği anlamına gelir. Fikir şu ki, bir süper bilgisayar üzerinde zamanları olmadıkça, şifreyi karma için zorlamak için yeterli kaynağa sahip olmayacaklardır. Belirli bir tuz için bir hash tablosu oluşturmak için zamanı büyük ölçüde yavaşlatacaktır.

Açıklaman için teşekkürler. "Karma 2 oluşturma" tuz ve karmanın birlikte ezildiğinden söz ederseniz, bunun AspNetUsers tablosundaki PasswordHash'te depolanıp depolanmadığını biliyor musunuz? Tuz görmem için herhangi bir yerde saklanıyor mu?
unicorn2

1
@ unicorn2 Andrew Savinykh'ın cevabına bir göz atarsanız ... Karma ile ilgili bölümde tuz, Base64 kodlu ve veritabanına yazılan bayt dizisinin ilk 16 baytında depolanmış gibi görünüyor. Bu Base64 kodlu dizeyi PasswordHash tablosunda görebilirsiniz. Base64 dizesi hakkında söyleyebileceğiniz tek şey, kabaca ilk üçte birinin tuz olmasıdır. Anlamlı tuz, PasswordHash tablosunda saklanan tam dizenin Base64 kodlu sürümünün ilk 16
Nattrass

@Nattrass, Haşhaş ve tuzları anlamam oldukça basit, ama tuz karma paroladan kolayca çıkarılırsa, ilk etapta tuzlamanın anlamı nedir. Tuzun, hash algoritmasına kolayca tahmin edilemeyen ekstra bir girdi olması gerektiğini düşündüm.
NSouth

1
@NSouth Benzersiz tuz, hash'i belirli bir parola için benzersiz kılar. Yani iki özdeş şifrenin farklı karması olacaktır. Karma ve tuzunuza erişiminiz hala saldırgana şifrenizi hatırlamıyor. Karma geri çevrilemez. Yine de mümkün olan her şifreyle oraya zorlamalıdırlar. Benzersiz tuz, korsanın, tüm kullanıcı tablonuzu ele geçirmeyi başarmışlarsa, belirli karma değerlerde bir frekans analizi yaparak ortak bir parola çıkaramadığı anlamına gelir.
Nattrass

8

Benim gibi bu konuda yepyeni olanlar için, burada const ile kod ve byte [] 'ları karşılaştırmanın gerçek bir yolu var. Stackoverflow tüm bu kodu aldım ama tanımlanmış consts böylece değerleri değiştirilebilir ve ayrıca

// 24 = 192 bits
    private const int SaltByteSize = 24;
    private const int HashByteSize = 24;
    private const int HasingIterationsCount = 10101;


    public static string HashPassword(string password)
    {
        // http://stackoverflow.com/questions/19957176/asp-net-identity-password-hashing

        byte[] salt;
        byte[] buffer2;
        if (password == null)
        {
            throw new ArgumentNullException("password");
        }
        using (Rfc2898DeriveBytes bytes = new Rfc2898DeriveBytes(password, SaltByteSize, HasingIterationsCount))
        {
            salt = bytes.Salt;
            buffer2 = bytes.GetBytes(HashByteSize);
        }
        byte[] dst = new byte[(SaltByteSize + HashByteSize) + 1];
        Buffer.BlockCopy(salt, 0, dst, 1, SaltByteSize);
        Buffer.BlockCopy(buffer2, 0, dst, SaltByteSize + 1, HashByteSize);
        return Convert.ToBase64String(dst);
    }

    public static bool VerifyHashedPassword(string hashedPassword, string password)
    {
        byte[] _passwordHashBytes;

        int _arrayLen = (SaltByteSize + HashByteSize) + 1;

        if (hashedPassword == null)
        {
            return false;
        }

        if (password == null)
        {
            throw new ArgumentNullException("password");
        }

        byte[] src = Convert.FromBase64String(hashedPassword);

        if ((src.Length != _arrayLen) || (src[0] != 0))
        {
            return false;
        }

        byte[] _currentSaltBytes = new byte[SaltByteSize];
        Buffer.BlockCopy(src, 1, _currentSaltBytes, 0, SaltByteSize);

        byte[] _currentHashBytes = new byte[HashByteSize];
        Buffer.BlockCopy(src, SaltByteSize + 1, _currentHashBytes, 0, HashByteSize);

        using (Rfc2898DeriveBytes bytes = new Rfc2898DeriveBytes(password, _currentSaltBytes, HasingIterationsCount))
        {
            _passwordHashBytes = bytes.GetBytes(SaltByteSize);
        }

        return AreHashesEqual(_currentHashBytes, _passwordHashBytes);

    }

    private static bool AreHashesEqual(byte[] firstHash, byte[] secondHash)
    {
        int _minHashLength = firstHash.Length <= secondHash.Length ? firstHash.Length : secondHash.Length;
        var xor = firstHash.Length ^ secondHash.Length;
        for (int i = 0; i < _minHashLength; i++)
            xor |= firstHash[i] ^ secondHash[i];
        return 0 == xor;
    }

Özel ApplicationUserManager'ınızda, PasswordHasher özelliğini yukarıdaki kodu içeren sınıfın adını ayarlarsınız.


Bunun için .. _passwordHashBytes = bytes.GetBytes(SaltByteSize); Sanırım bunu demek _passwordHashBytes = bytes.GetBytes(HashByteSize);
istediniz
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.