Mật khẩu mặc định của ASP.NET Identity - Nó hoạt động như thế nào và có an toàn không?


162

Tôi đang tự hỏi về mật khẩu Hasher được mặc định triển khai trong UserManager đi kèm với MVC 5 và ASP.NET Identity Framework, có đủ an toàn không? Và nếu vậy, nếu bạn có thể giải thích cho tôi làm thế nào nó hoạt động?

Giao diện IPasswordHasher trông như thế này:

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

Như bạn có thể thấy, nó không mất muối, nhưng nó được đề cập trong chủ đề này: " Băm mật khẩu nhận dạng Asp.net " rằng nó không tạo ra muối trong hậu trường. Vì vậy, tôi tự hỏi làm thế nào nó làm điều này? Và muối này đến từ đâu?

Mối quan tâm của tôi là muối là tĩnh, khiến nó khá không an toàn.


Tôi không nghĩ điều này trả lời trực tiếp câu hỏi của bạn, nhưng Brock Allen đã viết về một số mối quan tâm của bạn ở đây => brockallen.com/2013/10/20/ và cũng đã viết một thư viện xác thực và quản lý nhận dạng người dùng nguồn mở có nhiều thư viện khác nhau các tính năng của nồi hơi như đặt lại mật khẩu, băm, v.v. github.com/brockallen/Brock ALLen.MembershipReboot
Shiva

@Shiva Cảm ơn, tôi sẽ xem xét thư viện và video trên trang. Nhưng tôi thà không phải đối phó với một thư viện bên ngoài. Không nếu tôi có thể tránh nó.
André Snede Kock

2
FYI: stackoverflow tương đương cho bảo mật. Vì vậy, mặc dù bạn sẽ thường nhận được một câu trả lời tốt / chính xác ở đây. Các chuyên gia trên security.stackexchange.com đặc biệt là nhận xét "có an toàn không" Tôi đã hỏi một loại câu hỏi tương tự và độ sâu cũng như chất lượng của câu trả lời thật đáng kinh ngạc.
phil soady

@philsoady Cảm ơn, điều đó có ý nghĩa tất nhiên, tôi đã ở một vài "diễn đàn phụ" khác, nếu tôi không nhận được câu trả lời, tôi có thể sử dụng, tôi sẽ chuyển sang securiry.stackexchange.com. Và cảm ơn vì tiền boa!
André Snede Kock

Câu trả lời:


227

Đây là cách triển khai mặc định ( ASP.NET Framework hoặc ASP.NET Core ) hoạt động. Nó sử dụng Hàm dẫn xuất chính với muối ngẫu nhiên để tạo ra hàm băm. Muối được bao gồm như là một phần của đầu ra của KDF. Do đó, mỗi lần bạn "băm" cùng một mật khẩu, bạn sẽ nhận được các giá trị băm khác nhau. Để xác minh hàm băm, đầu ra được chia lại cho muối và phần còn lại, và KDF được chạy lại trên mật khẩu với muối đã chỉ định. Nếu kết quả khớp với phần còn lại của đầu ra ban đầu, hàm băm được xác minh.

Băm:

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

Đang xác minh:

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
Vì vậy, nếu tôi hiểu điều này một cách chính xác, HashPasswordhàm, trả về cả hai trong cùng một chuỗi? Và khi bạn xác minh nó, nó sẽ tách nó ra một lần nữa và băm mật khẩu Cleartext đến, với muối từ phần tách và so sánh nó với hàm băm ban đầu?
André Snede Kock

9
@ AndréSnedeHansen, chính xác. Và tôi cũng khuyên bạn nên hỏi về bảo mật hoặc về mật mã SE. Phần "an toàn" có thể được giải quyết tốt hơn trong các bối cảnh tương ứng.
Andrew Savinykh

1
@shajeerpuzhakkal như được mô tả trong câu trả lời ở trên.
Andrew Savinykh

3
@AndrewSavinykh Tôi biết, đó là lý do tại sao tôi hỏi - vấn đề là gì? Để làm cho mã trông thông minh hơn? )
Andrew Cyrul

1
@ MihaiAlexandru-Iovy var hashedPassword = HashPassword(password); var result = VerifyHashedPassword(hashedPassword, password);- là những gì bạn cần làm. sau đó resultchứa đúng
Andrew Savinykh

43

Vì ngày nay ASP.NET là mã nguồn mở, bạn có thể tìm thấy nó trên GitHub: AspNet.Identity 3.0AspNet.Identity 2.0 .

Từ các ý kiến:

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

Có, và đáng chú ý, có những bổ sung cho thuật toán zespri đang hiển thị.
André Snede Kock

1
Nguồn trên GitHub là Asp.Net.Identity 3.0 vẫn đang trong giai đoạn phát hành trước. Nguồn của hàm băm 2.0 là trên CodePlex
David

1
Việc triển khai mới nhất có thể được tìm thấy trong github.com/dotnet/aspnetcore/blob/master/src/Identity/ tựa ngay bây giờ. Họ đã lưu trữ kho lưu trữ khác;)
FranzHuber23

32

Tôi hiểu câu trả lời được chấp nhận và đã bỏ phiếu cho nó nhưng nghĩ rằng tôi sẽ bỏ câu trả lời của giáo dân của tôi ở đây ...

Tạo một hàm băm

  1. Muối được tạo ngẫu nhiên bằng hàm Rfc2898DeriveBytes tạo ra hàm băm và muối. Đầu vào của Rfc2898DeriveBytes là mật khẩu, kích thước của muối để tạo và số lần lặp băm để thực hiện. https://msdn.microsoft.com/en-us/l Library / h83s4e12 (v = vs.110) .aspx
  2. Muối và băm sau đó được nghiền với nhau (trước tiên là muối sau đó là băm) và được mã hóa thành một chuỗi (vì vậy muối được mã hóa trong hàm băm). Băm được mã hóa này (chứa muối và băm) sau đó được lưu trữ (thường) trong cơ sở dữ liệu đối với người dùng.

Kiểm tra mật khẩu với hàm băm

Để kiểm tra mật khẩu mà người dùng nhập vào.

  1. Muối được chiết xuất từ ​​mật khẩu băm được lưu trữ.
  2. Muối được sử dụng để băm mật khẩu đầu vào của người dùng bằng cách sử dụng quá tải Rfc2898DeriveBytes , lấy muối thay vì tạo mật khẩu . https://msdn.microsoft.com/en-us/l Library / yx129kfs (v = vs.110) .aspx
  3. Băm được lưu trữ và băm thử nghiệm sau đó được so sánh.

Băm

Dưới vỏ bọc, hàm băm được tạo bằng hàm băm SHA1 ( https://en.wikipedia.org/wiki/SHA-1 ). Hàm này được gọi lặp lại 1000 lần (Trong triển khai Nhận dạng mặc định)

Tại sao điều này an toàn

  • Muối ngẫu nhiên có nghĩa là kẻ tấn công không thể sử dụng bảng băm được tạo trước để thử và phá mật khẩu. Họ sẽ cần phải tạo một bảng băm cho mỗi muối. (Giả sử ở đây rằng tin tặc cũng đã xâm phạm muối của bạn)
  • Nếu 2 mật khẩu giống hệt nhau, chúng sẽ có các giá trị băm khác nhau. (có nghĩa là kẻ tấn công không thể suy ra mật khẩu 'phổ biến')
  • Lặp đi lặp lại gọi SHA1 1000 lần có nghĩa là kẻ tấn công cũng cần phải làm điều này. Ý tưởng là trừ khi họ có thời gian trên siêu máy tính, họ sẽ không có đủ tài nguyên để buộc mật khẩu từ hàm băm. Nó sẽ ồ ạt làm chậm thời gian để tạo bảng băm cho một loại muối nhất định.

Cảm ơn lời giải thích của bạn. Trong phần "Tạo hàm băm 2." bạn đề cập rằng muối và hàm băm được trộn với nhau, bạn có biết nếu nó được lưu trữ trong PasswordHash trong bảng AspNetUsers. Là muối được lưu trữ bất cứ nơi nào cho tôi xem?
kỳ lân2

1
@ unicorn2 Nếu bạn xem câu trả lời của Andrew Savinykh ... Trong phần về băm, có vẻ như muối được lưu trữ trong 16 byte đầu tiên của mảng byte được mã hóa Base64 và ghi vào cơ sở dữ liệu. Bạn sẽ có thể thấy chuỗi được mã hóa Base64 này trong bảng PasswordHash. Tất cả những gì bạn có thể nói về chuỗi Base64 là khoảng một phần ba đầu tiên của nó là muối. Muối có ý nghĩa là 16 byte đầu tiên của phiên bản được giải mã Base64 của chuỗi đầy đủ được lưu trữ trong bảng PasswordHash
Nattrass

@Nattrass, Sự hiểu biết của tôi về băm và muối khá thô sơ, nhưng nếu muối dễ dàng được trích xuất từ ​​mật khẩu băm, thì điểm đầu tiên của muối là gì. Tôi nghĩ rằng muối có nghĩa là một đầu vào bổ sung cho thuật toán băm không thể dễ dàng đoán ra.
NSouth

1
@NSouth Muối duy nhất làm cho hàm băm duy nhất cho một mật khẩu nhất định. Vì vậy, hai mật khẩu giống hệt nhau sẽ có hàm băm khác nhau. Có quyền truy cập vào hàm băm và muối của bạn vẫn không khiến kẻ tấn công nhớ mật khẩu của bạn. Băm không thể đảo ngược. Họ vẫn sẽ cần phải vũ trang bằng mọi cách có thể. Muối duy nhất chỉ có nghĩa là tin tặc không thể suy ra một mật khẩu phổ biến bằng cách thực hiện phân tích tần suất trên các hàm băm cụ thể nếu chúng đã quản lý để giữ toàn bộ bảng người dùng của bạn.
Nattrass

8

Đối với những người như tôi là người hoàn toàn mới với điều này, đây là mã với const và một cách thực tế để so sánh byte [] 's. Tôi đã nhận được tất cả các mã này từ stackoverflow nhưng đã xác định các hằng số để các giá trị có thể được thay đổi và cả

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

Trong ApplicationUserManager tùy chỉnh của bạn, bạn đặt thuộc tính PasswordHasher tên của lớp chứa mã ở trên.


Đối với điều này .. _passwordHashBytes = bytes.GetBytes(SaltByteSize); Tôi đoán bạn có ý này _passwordHashBytes = bytes.GetBytes(HashByteSize);.. Không quan trọng trong kịch bản của bạn vì cả hai đều có cùng kích thước nhưng nói chung ..
Akshatha
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.