Domanda

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);
    }
}
È stato utile?

Soluzione

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.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top