Issue With Encrypted Password in C# using Rfc2898DeriveBytes and MSSQL

667 Views Asked by At

I have made a login system for my application, however, it is working extremely inconsistently.

Sometimes the password will work, but other times it says it's incorrect. I'm 100% sure I'm typing it correctly.

To store the passwords, I generate a random salt, hash the password with the salt and store the hashed password and salt along with the username in the database.

To authenticate the user, I select the hashed password and salt based on the given username. I then hash their password attempt with the salt and see if it matches their original hashed password, allowing them to log in if so.

My code is as follows:

private const int NumberOfRounds = 5000, SaltLength = 32;
public static byte[] GenerateSalt()
{
    using (var rng = new RNGCryptoServiceProvider())
    {
        var randomNumber = new byte[SaltLength];

        rng.GetBytes(randomNumber);

        return randomNumber;
    }
}

public static byte[] HashPassword(byte[] passwordToHash, byte[] salt)
{
    using (var deriveBytes = new Rfc2898DeriveBytes(passwordToHash, salt, numberOfRounds))
    {
        return deriveBytes.GetBytes(32);
    }
}



public static bool IsPasswordValid(string inputPassword, string hashedPassword, string salt)
{
    byte[] potentialValidPassword = HashPassword(Encoding.Unicode.GetBytes(inputPassword),
                Encoding.Unicode.GetBytes(salt));

    string potentialAsString = Encoding.Unicode.GetString(potentialValidPassword);

    return  Encoding.Unicode.GetBytes(hashedPassword).SequenceEqual(potentialValidPassword) ||
                hashedPassword.Equals(potentialAsString);
}

The reason my check compares both the byte array value and the string value is that sometimes the byte value comparison fails but the string value works.

My code to insert a user into the database is as follows

public SecurityReturnMessage AddUser(string username, string password)
{
    byte[] salt = PasswordManagement.GenerateSalt();

    byte[] hashedPassword = PasswordManagement.HashPassword(Encoding.Unicode.GetBytes(password), salt);

    if (conn.State != ConnectionState.Open)
        conn.Open();

    int result;
    using (IDbCommand comm = conn.CreateCommand())
    {
        comm.CommandText = "usp_IFRS_SEC_USER_INSERT";
        comm.CommandType = CommandType.StoredProcedure;

        SqlParameter hashPwd =new SqlParameter("@hashpwd", SqlDbType.NVarChar)
        {
            Value = Encoding.Unicode.GetString(hashedPassword)
        };

         SqlParameter saltParameter = new SqlParameter("@salt", SqlDbType.NVarChar)
         {
             Value = Encoding.Unicode.GetString(salt)
         };

         comm.Parameters.Add(Extensions.CreateParameter(comm, "@user", username));
         comm.Parameters.Add(hashPwd);
         comm.Parameters.Add(saltParameter);

         var returnVal = comm.CreateParameter();
         returnVal.Direction = ParameterDirection.ReturnValue;
         comm.Parameters.Add(returnVal);

         comm.ExecuteNonQuery();

         result = (int)returnVal.Value;
     }

     if (conn.State != ConnectionState.Closed)
         conn.Close();

     return (SecurityReturnMessage)result;
}

If anyone could help me out with this I'd be extremely grateful.

1

There are 1 best solutions below

4
Ranjani On

I Suggest you to use the Base64 conversions as below when you convert values from Bytes to String and vice versa.

    string base64 = Convert.ToBase64String(bytes);
    byte[] bytes = Convert.FromBase64String(base64);

This way you can make sure that the unicode data is not lost or corrupted during either of the conversion process, which might be a valid reason for your code's inconsistency.