Search code examples
c#validationencryptionbcryptverification

Bcrypt validate password


I'm trying to store hashed passwords in db. Here is my code:

 string passwords = textBox2.Text;
 string salt = BCrypt.Net.BCrypt.GenerateSalt(12);
 string hashPwd = BCrypt.Net.BCrypt.HashPassword(passwords, salt);
        try
        {
            SQLiteCommand command = new SQLiteCommand();
            connection.Open();
            command.Connection = connection;
            command.CommandText = ((@"INSERT INTO acc (UserName, Pass) VALUES ('" + textBox1.Text + "','" + hashPwd+ "');"));
            command.ExecuteNonQuery();
            connection.Close();
        }
        catch (Exception ex)
        {
            MessageBox.Show("Error:" + ex.ToString());
            return;
        }

Login/verification code:

          try
          {   

            SQLiteDataAdapter sqlad = new SQLiteDataAdapter("SELECT  COUNT(*) From acc WHERE Username = '" + textBox1.Text + "' AND Pass = '" + textBox2.Text + "' ", connection);
            DataTable dt = new DataTable();
            sqlad.Fill(dt);`
            string userid = dt.Rows[0]["UserName"].ToString();
            string password = dt.Rows[0]["Pass"].ToString();
            bool flag = BCrypt.Net.BCrypt.Verify(textBox2.Text, password);

            if (userid == textBox1.Text && flag == true)
            {
                Form2 frm = new Form2();
                frm.Show();
            }
            else
            {
                MessageBox.Show("Invalid UserId or password");
            }
          }
          catch (Exception ex)
          {
            MessageBox.Show(ex.ToString());
            return;
          }  

I can't verify Password, I'm getting error, could you help me please? One more question, should I save salt too in database?


Solution

  • There are a couple of problems with your code:

    1. SQL Injection

    Both your insert and verification code blocks are vulnerable to SQL injection, since they allow text you take directly from user input into the SQL string executed, a vulnerability they can use to either subvert the login check, or destroy your database. Don't do it!

    2. Your selection of the hashed password back from the database does not select the hashed password.. or anything of interest.

    Consider what you have here:

    SQLiteDataAdapter sqlad = new SQLiteDataAdapter(@"
        SELECT  
            COUNT(*) 
        From 
            acc 
        WHERE 
           Username = '" + textBox1.Text + "' 
           AND 
           Pass = '" + textBox2.Text + "' ", connection);
    

    So, let's say I gave my username as "Steve" and password as "hello", which got hashed to "ab123cdef", and inserted to your acc table as:

    UserName    Pass
    Steve       ab123cdef
    

    And when I come to verify this with the original correct user and password, your select statement says "give me the number of rows with username 'Steve' and pass 'hello'", which will duly return zero.

    Your code should throw an exception here:

    string userid = dt.Rows[0]["UserName"].ToString();
    

    Since the result set doesn't contain the username as an output.

    Here is a basic little example using the libraries you've chosen to show how you could insert and verify a password successfully.

    Regarding what to do with the salt, the function HashPassword has prepended the salt to the password hash, so if you store the output of this, you are storing the salt. The verify function you use in verification will handle and check this for you.

    static void CreateUser(string username, string password)
    {
        if (UserExists(username))
            throw new InvalidOperationException("User already exists");
    
        string salt = BCrypt.Net.BCrypt.GenerateSalt(12);
        // if you look at the hashed password, notice that it's prepended with the salt generated above
        string hashedPassword = BCrypt.Net.BCrypt.HashPassword(password, salt);
    
        using (SQLiteConnection connection = new SQLiteConnection(connectionString))
        {
            connection.Open();
            SQLiteCommand insertCommand = new SQLiteCommand(connection);
            insertCommand.CommandText = @"INSERT INTO acc (UserName, Pass) VALUES (@username, @hashedPass);";
            // use parameterised queries to mitigate sql injection
            insertCommand.Parameters.Add(new SQLiteParameter("@username", username));
            insertCommand.Parameters.Add(new SQLiteParameter("@hashedPass", hashedPassword));
            insertCommand.ExecuteNonQuery();
        }
    }
    

    To verify a given username/password, all we need back from the database is the output of the hash function to verify against what we've been given.

    static bool Verify(string username, string password)
    {
        using (SQLiteConnection connection = new SQLiteConnection(connectionString))
        {
            connection.Open();
    
            SQLiteCommand checkUserCommand = new SQLiteCommand(connection)
            {
                CommandText = @"SELECT Pass FROM acc WHERE UserName = @username;"
            };
    
            checkUserCommand.Parameters.Add(new SQLiteParameter("@username", username));
            var hashedPassword = (string)checkUserCommand.ExecuteScalar();                
            return BCrypt.Net.BCrypt.Verify(password, hashedPassword);                
        }
    }
    

    Usage would be something like..

    if (!UserExists(username))
    {
        CreateUser(username, password);
        Console.WriteLine("User {0} created", username);
    }
    else
    {
        bool loginOk = Verify(username, password);
        Console.WriteLine("Login ok?: {0}", loginOk);
    }