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?
The Rfc2898DeriveBytes class can be used to produce a derived key from a base key and other parameters. In a password-based key derivation function, the base key is a password and the other parameters are a salt value and an iteration count.
Technically speaking SHA512 password hashes are not cracked or decrypted . They are matched using a list of possible passwords, it is more akin to reversing than breaking.
For those coming to this via a search, the answer is that the Rfc2898DeriveBytes
constructor takes the hash algorithm as a parameter, and HashAlgorithmName.SHA512
is one supported option. So, this works:
var pbkdf2 = new Rfc2898DeriveBytes(passwordBytes, bytesSalt, 1000000, HashAlgorithmName.SHA512);
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.
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.
Your API is awkward to use, since you push salt management onto the caller instead of handling it within the Create
/Verify
functions.
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.
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).
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.
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 learn.microsoft.com.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With