0

I have a function to calculate a SHA256 hash with a salt. The salt is then appended onto the hashed password and stored. The function for doing this looks like so:

public static string CalculateHash(string input)
{
    var inputBuffer = new List<byte>(Encoding.Unicode.GetBytes(input));

    var saltBytes = new byte[16];
    using (var rnd = RandomNumberGenerator.Create())
    {
        rnd.GetBytes(saltBytes);
    }

    inputBuffer.AddRange(saltBytes);

    byte[] hashedBytes;
    using (var hasher = new SHA256Managed())
    {
        hashedBytes = hasher.ComputeHash(inputBuffer.ToArray());
    }

    var hash = BitConverter.ToString(hashedBytes).Replace("-", string.Empty);
    var salt = BitConverter.ToString(saltBytes).Replace("-", string.Empty);

    return string.Format("{0}:{1}", hash, salt);
}

It stores a string of 97 characters (including the ':') in length and seems to work well. I am struggling to strip the salt off of the hash when I retrieve it back. The issue I am having is converting the salt, in string form, back to a byte[16], which contains the original bytes. I assume once I have these original 16 bytes, I can append them to the user input, and hash the passwords to check for a match.

My current attempt is to split the hashed password and the salt at the colon delimiter and use a GetBytes function. It works like this:

public static bool ValidatePassword(string password, string hashWithSalt)
{
    var split = hashWithSalt.Split(':');
    var salt = split[1];

    var saltBytes = GetBytes(salt);
}

private static byte[] GetBytes(string str)
{
    var bytes = new byte[str.Length * sizeof(char)];
    Buffer.BlockCopy(str.ToCharArray(), 0, bytes, 0, bytes.Length);
    return bytes;
}

But GetBytes is currently returning back byte[64], which is clearly not the original byte format. How can I go about fixing this?

Frank Johnson
  • 163
  • 1
  • 12
  • 2
    Normally you add the salt and *then* hash it. Then you hash the provided password the same way to validate. Is there a reason you aren't doing this? – BradleyDotNET Jun 18 '14 at 16:50
  • 1
    @BradleyDotNET: The OP's already doing that: `inputBuffer.AddRange(saltBytes)`. He's storing the salt alongside the hash of (password+salt). – Jon Skeet Jun 18 '14 at 16:52
  • 1
    Currently your code is *encoding* the salt as hex... but you're not doing any hex *parsing* anywhere. I can't think of anywhere that's available out of the box in .NET, although it's easy enough to achieve. A simpler option might just be to use `Convert.ToBase64String` and `Convert.FromBase64String` though. – Jon Skeet Jun 18 '14 at 16:54
  • @JonSkeet That was the issue. I can use a simple LINQ expression to sort that out as seen [link](http://stackoverflow.com/questions/321370/convert-hex-string-to-byte-array) – Frank Johnson Jun 18 '14 at 17:01
  • @FrankJohnson: Oh absolutely - it's just more code than calling `Convert.*Base64String` :) – Jon Skeet Jun 18 '14 at 17:03
  • "I can prepend them to the user input," - I would _append_ them, just like the first time. – H H Jun 18 '14 at 17:04
  • How about using a secure password hash like PBKDF2, bcrypt or scrypt instead of SHA-256? – CodesInChaos Jun 18 '14 at 18:33
  • @CodesInChaos Speed, security -- preference really. – Frank Johnson Jun 18 '14 at 20:16
  • @FrankJohnson Pretty much the whole point of a password hash is being expensive compute. Thus a fast hash like SHA256 where a single GPU can verify *a billion* password candidates per second is a really bad choice, almost as bad as MD5. This choice isn't just a preference -- it's objectively bad. See [How to securely hash passwords?](http://security.stackexchange.com/questions/211/how-to-securely-hash-passwords) – CodesInChaos Jun 19 '14 at 10:19

1 Answers1

0

Figured out my own solution. Just needed to change the GetBytes function to handle the hex conversion.

private static byte[] GetBytes(string str)
{
    return Enumerable.Range(0, str.Length)
            .Where(x => x % 2 == 0)
            .Select(x => Convert.ToByte(str.Substring(x, 2), 16))
            .ToArray();
}
Frank Johnson
  • 163
  • 1
  • 12