How do I use SHA-512 with Rfc2898DeriveBytes in my

2020-06-13 19:48发布

问题:

I'm completely new to cryptography, but learning. I've pieced together many different suggestions from my research online, and have made my own class for handling the hash, salt, key stretching, and comparison/conversion of associated data.

After researching the built-in .NET library for cryptography, I discovered that what I have is still only SHA-1. But I'm coming to the conclusion that it's not bad since I'm using multiple iterations of the hash process. Is that correct?

But if I wanted to start with the more robust SHA-512, how could I implement it in my code below? Thanks in advance.

using System;
using System.Runtime.InteropServices;
using System.Security;
using System.Security.Cryptography;

public class CryptoSaltAndHash
{
    private string strHash;
    private string strSalt;
    public const int SaltSizeInBytes = 128;
    public const int HashSizeInBytes = 1024;
    public const int Iterations = 3000;

    public string Hash { get { return strHash; } }
    public string Salt { get { return strSalt; } }

    public CryptoSaltAndHash(SecureString ThisPassword)
    {
        byte[] bytesSalt = new byte[SaltSizeInBytes];
        using (RNGCryptoServiceProvider crypto = new RNGCryptoServiceProvider())
        {
            crypto.GetBytes(bytesSalt);
        }
        strSalt = Convert.ToBase64String(bytesSalt);
        strHash = ComputeHash(strSalt, ThisPassword);
    }

    public static string ComputeHash(string ThisSalt, SecureString ThisPassword)
    {
        byte[] bytesSalt = Convert.FromBase64String(ThisSalt);
        Rfc2898DeriveBytes pbkdf2 = new Rfc2898DeriveBytes(
            convertSecureStringToString(ThisPassword), bytesSalt, Iterations);
        using (pbkdf2)
        {
            return Convert.ToBase64String(pbkdf2.GetBytes(HashSizeInBytes));
        }
    }

    public static bool Verify(string ThisSalt, string ThisHash, SecureString ThisPassword)
    {
        if (slowEquals(getBytes(ThisHash), getBytes(ComputeHash(ThisSalt, ThisPassword))))
        {
            return true;
        }
        return false;
    }

    private static string convertSecureStringToString(SecureString MySecureString)
    {
        IntPtr ptr = IntPtr.Zero;
        try
        {
            ptr = Marshal.SecureStringToGlobalAllocUnicode(MySecureString);
            return Marshal.PtrToStringUni(ptr);
        }
        finally
        {
            Marshal.ZeroFreeGlobalAllocUnicode(ptr);
        }
    }

    private static bool slowEquals(byte[] A, byte[] B)
    {
        int intDiff = A.Length ^ B.Length;
        for (int i = 0; i < A.Length && i < B.Length; i++)
        {
            intDiff |= A[i] ^ B[i];
        }
        return intDiff == 0;
    }

    private static byte[] getBytes(string MyString)
    {
        byte[] b = new byte[MyString.Length * sizeof(char)];
        System.Buffer.BlockCopy(MyString.ToCharArray(), 0, b, 0, b.Length);
        return b;
    }
}

Notes: I've referenced a lot of practices from https://crackstation.net/hashing-security.htm. The slowEquals comparison method is to normalize execution time by preventing branching. The usage of SecureString is to have an encrypted form of the password pass between this class and other classes and pages within my web application. While this site will be over HTTPS, it's always nice to go the extra mile to ensure things are as secure as possible while still being within reason.

In my code, I've set the key string to 128 bytes (though it grows bigger sometimes, which is fine), the hash size to 1KB, and the number of iterations at 3,000. It's a little larger than the typical 64 byte salt, 512 byte hash, and 1,000 or 2,000 iterations, but then again login speed and app performance is an extremely low priority.

Thoughts?

回答1:

If anyone encounters this question by search, now Microsoft provides Microsoft.AspNetCore.Cryptography.KeyDerivation NuGet package, which allows to use PBKDF2 with SHA-256 and SHA-512 hash functions. Documentation is available at docs.microsoft.com.



回答2:

  1. 3000 iterations is quite low. Even 10000 is low. But you need to weight the security gain of additional iterations against the risk that an attacker DoSes your server by trying to login often, which triggers an expensive hash for each attempt.
  2. There is no point in a salt larger than 128 bits/16 bytes. A salt should be unique, nothing more.
  3. A hash size larger than the native size (20 bytes for SHA-1) reduces performance for the defender but not for the attacker. Since this means you can afford fewer iterations, it actually weakens security.

    For example at the same cost as your 1024 byte hash with 3000 iterations, you could afford a 20 byte hash with 156000 iterations, which is 52 times more expensive to crack.

  4. To use SHA-2 you'll need a completely different PBKDF2 implementation, the one included with .net is hardcoded to use SHA-1.

    If you bother to use a third party library, I'd rather use a bcrypt library since that's much stronger against GPU based attackers.

  5. Your API is awkward to use, since you push salt management onto the caller instead of handling it within the Create/Verify functions.

  6. It's silly to use SecureString and then to convert it to String. This counteracts the whole point of using a SecureString in the first place.

    Personally I wouldn't bother with SecureString in a typical application. It's only worthwhile if you combine it with an extensive whole-stack security review that checks that the password is never stored in a String and always erased from mutable storage once it's no longer required.

  7. I wouldn't store passwords/salts in instance variables. Just keep them local to the relevant functions. I'd only store configuration in the instance variables (such as iteration count).

  8. While SHA-1 is weakened cryptographically, the attacks produce collisions. For password hashing collisions are irrelevant, what you care about are first pre-image attacks. SHA-1 is still pretty strong in that regard.

    The main advantage of SHA-512 is not that it's cryptographically stronger (though it is), it's that 64 bit arithmetic costs the attacker more than the defender, since the defender will probably use a 64 bit Intel CPU which offers fast 64 bit arithmetic.



回答3:

Answering the question: Download the free code samples from "SecurityDriven.NET" book. Find the PBKDF2 class which takes an HMAC factory. HMACSHA512 factory is available, among others.

Since you're new to cryptography, I also strongly suggest you read the book (ex. to fully understand the points that CodesInChaos made).