0

History: Password hashing used to work, now I can't get into my software

Since writing the question above (basically, just can't log in and didn't know why), I've determined that the byte arrays for the password I entered when I logged in and the password I entered when I created the user record, are completely different:

Here's the bytes from the database
Here's the bytes from the password when I failed to logged in

Now that I know that the value being passed to the database (or the value stored in the database) are wrong, I'm at a loss as to how I should fix it...

I'm not even sure why this used to work, but suddenly broke.

Thanks in advance for any suggestions!

EDIT

Here's the user login and registration code

    public User Login() // returns an instance of its own class
    {
        var auser = ie.users.FirstOrDefault(u => String.Compare(u.emailaddress, emailaddress, false) == 0);

        if (auser == null)
        {
            throw new ValidationException("User not found");
        }

        // get bytes for password in the database
        byte[] passwordbytes = CryptUtility.GetBytes(auser.password);

        HashGenerator h = new HashGenerator(password, auser.usersalt);
        string hash = h.GetHash();

        var us = ie.users.FirstOrDefault(u => String.Compare(u.emailaddress, emailaddress, false) == 0 && String.Compare(u.password, password, false) == 0);

        if (us == null)
        {
            throw new Exception("Invalid email address and/or password.");
        }
        User user = new User();

        user.userid = us.userid;
        user.storeid = us.storeid;
        user.role = us.role;

        return user;
    }

    public void Add() // user registration
    {
        user newuser = new user();
        newuser.storeid = storeid;
        newuser.emailaddress = emailaddress;

        // Generate password hash
        string usersalt = SaltGenerator.GetSaltString();
        HashGenerator hash = new HashGenerator(password, usersalt);
        newuser.password = hash.GetHash();

        newuser.role = role;
        newuser.usersalt = usersalt;

        ie.users.Add(newuser);
        ie.SaveChanges();
    }

Here's the security code to generate hash and salt values and their byte/string values as well

public class HashGenerator
{
    public string pass { get; protected set; }
    public string salt { get; protected set; }

    public HashGenerator(string Password, string Salt)
    {
        pass = Password;
        salt = Salt;
    }

    public string GetHash()
    {
        SHA512 sha = new SHA512CryptoServiceProvider();
        byte[] data = CryptUtility.GetBytes(String.Format("{0}{1}", pass, salt));
        byte[] hash = sha.ComputeHash(data);

        return CryptUtility.GetString(hash);
    }
}

public static class SaltGenerator
{
    private static RNGCryptoServiceProvider provider = null;
    private const int saltSize = 128;

    static SaltGenerator()
    {
        provider = new RNGCryptoServiceProvider();
    }

    public static string GetSaltString()
    {
        byte[] saltBytes = new byte[saltSize];

        provider.GetNonZeroBytes(saltBytes);

        return CryptUtility.GetString(saltBytes);
    }
}

class CryptUtility
{
    public static byte[] GetBytes(string Str)
    {
        byte[] bytes = new byte[Str.Length * sizeof(char)];
        System.Buffer.BlockCopy(Str.ToCharArray(), 0, bytes, 0, bytes.Length);
        return bytes;
    }

    public static string GetString(byte[] Bytes)
    {
        char[] chars = new char[Bytes.Length / sizeof(char)];
        System.Buffer.BlockCopy(Bytes, 0, chars, 0, Bytes.Length);
        return new string(chars);
    }
}
6
  • 1
    We need more details, add your code for login and registering. Commented May 12, 2014 at 13:21
  • I would guess that the lib that you use for it should have changed! But you need to support more info on how you do it. Commented May 12, 2014 at 13:23
  • @BertrandC. added the code for ya Commented May 12, 2014 at 13:36
  • @Ortund Likely unrelated, but please, don't use those GetBytes and GetString methods! It's a dirty hack, not a reliable way to convert char array to byte array and back. Always use an encoding - e.g. Encoding.UTF8.GetBytes() and Encoding.UTF8.GetString() respectively. There's many ways the same unicode string can be represented in memory (little endian vs. big endian being one possible issue, unicode normalization another, there's chars longer than sizeof(char) etc.) - your code relies on byte[] equality, so don't guess that it will be always the same - use proper encoding. Commented May 12, 2014 at 13:48
  • @Ortund Oh, and now that I'm looking more closely, scratch that "likely unrelated" part. Given the amount of GetBytes + GetString you're doing, it's very likely you've ended up with invalid unicode strings at some point. string is not a byte array. Joel Spolsky has a great article on the topic - joelonsoftware.com/articles/Unicode.html Commented May 12, 2014 at 13:50

1 Answer 1

2

I've pretty much said everything in the comments, but looking at your other question and such, I feel I have to post this as an answer, because it's absolutely the solution to your problem.

Unicode is a complicated charset (http://www.joelonsoftware.com/articles/Unicode.html - do read it). The important thing you have to realize that there's no 1:1 mapping between a unicode string and a byte array. One char can have a single byte, or it can have many more. More importantly, there's a lot of magic chars, so "casting" a general byte array to a string can very easily result in an invalid unicode string, or it can malform your data!

This is why it worked for you when your column was varchar. In varchar, every char has a 1:1 mapping to a byte, as simple as that. So "casting" a byte array to a varchar and back doesn't lead to data changes or data loss. This no longer applies for nvarchar, because nvarchar is a unicode string (just like .NETs string).

Instead, save the password hash as a byte array (binary or varbinary), or, convert the byte array to a Base-64 string - that's the commonly used pattern when you need both plain-text and hashed password for example. So your GetBytes() would be doing Convert.FromBase64String, and GetString would be doing Convert.ToBase64String.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.