Link to home
Start Free TrialLog in
Avatar of Brian
BrianFlag for United States of America

asked on

Salt + Hash Password

Hello Experts,

I have the following code attached and I need a way to SALT and HASH the password in my DB. I also need to authenticate the user based on the SALT and HASH stored in the DB. I'm confused as to the best method to handle this. Do i need to store the SALTED and HASHED Password in the same field in my DB? Or do I store the HASHED Password in it's own field labeled users_password and then store the SALTED value in another field labeled users_password_salted?

I would like to use SHA512 and you will see what I have already which works fine and authenticate fine but I don't know if I"m storing the SALT and HASH correctly and also not sure if I should be using GUID. I found a tutorial that should using GUID but also found another tutorial that explained everything differently which I liked this tutorials explaination but don't know how to implement it. That tutorial is listed below.

I'm also not sure what field type and size I should be using. I'm using varchar(50) now but not sure if that is correct to use.

http://crackstation.net/hashing-security.html


protected void btn_NewUser_Click(object sender, EventArgs e)
    {
        using (SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["HealthCourses"].ConnectionString))
        {
            SqlCommand cmd = new SqlCommand();
            cmd.CommandText = "HealthCourses_InsertUsers";
            cmd.CommandType = CommandType.StoredProcedure;
            cmd.Connection = conn;

            cmd.Parameters.AddWithValue("@c_id", SqlDbType.Int).Value = ddlClient.SelectedItem.Value;
            cmd.Parameters.AddWithValue("@bldg_id", SqlDbType.Int).Value = ddlBldgLocation.SelectedItem.Value;
            cmd.Parameters.AddWithValue("@s_id", SqlDbType.Int).Value = ddlState.SelectedItem.Value;
            cmd.Parameters.AddWithValue("@users_flname", SqlDbType.VarChar).Value = txtName.Text;
            cmd.Parameters.AddWithValue("@users_street_address", SqlDbType.VarChar).Value = txtHomeAddress.Text;
            cmd.Parameters.AddWithValue("@users_city", SqlDbType.VarChar).Value = txtCity.Text;
            cmd.Parameters.AddWithValue("@users_zip", SqlDbType.VarChar).Value = txtZipCode.Text;
            cmd.Parameters.AddWithValue("@users_phone", SqlDbType.VarChar).Value = txtHomePhone.Text;
            cmd.Parameters.AddWithValue("@users_work_ext", SqlDbType.VarChar).Value = txtWorkExtension.Text;
            cmd.Parameters.AddWithValue("@users_email", SqlDbType.VarChar).Value = txtEmailAddress.Text;
            cmd.Parameters.AddWithValue("@users_username", SqlDbType.VarChar).Value = txtUserName.Text;

            //Create instance of SHA512CryptoServiceProvider
            SHA512CryptoServiceProvider SHA512Hash = new SHA512CryptoServiceProvider();

            byte[] hashedBytes = null;
            UTF8Encoding encoder = new UTF8Encoding();

            string guid = System.Guid.NewGuid().ToString(); //36 long characters
            hashedBytes = SHA512Hash.ComputeHash(encoder.GetBytes(txtPassword.Text + guid));

            cmd.Parameters.AddWithValue("@users_password", SqlDbType.VarChar).Value = hashedBytes;
            cmd.Parameters.AddWithValue("@users_password_salt", SqlDbType.VarChar).Value = guid;

            try
            {
                conn.Open();

                cmd.ExecuteNonQuery();

                Response.Redirect("newuser_success.aspx");
            }

            catch (Exception ex)
            {
                btn_NewUser.Enabled = true;
                lblInsertError.Text = ex.Message.ToString();
            }

            finally
            {
                conn.Close();
            }
        }
    }

Open in new window

Avatar of Carlos Villegas
Carlos Villegas
Flag of United States of America image

Hello asp_net2, you can accomplish that without using a guid, instead you can use your internal user id (if there is one) plus some static salt to simplify things, also I recommend you to split the user creation and the set password logic.

I can help you with your design, can you post the HealthCourses_InsertUsers code and the definition of the table where the data is inserted?
Avatar of Brian

ASKER

Hi yv989c,

First of all thank you very much for helping me with this post. I'm not all that knowledgeable with Salting + Hashing hence the reason I'm here, but hope to learn a thing or to :)

I'm attaching the Stored Procedure, you already have the CODEBEHIND that I'm using above.

However, I'm not sure what you are suggesting but I would like to possible stick to the way it was mentioned in the URL here http://crackstation.net/hashing-security.html only because that makes some sense to me. Could you explain why your method may be better than the one in the URL? I'm only asking because I don't know and this is confusing me because I thought there was only one correct way to handle the Salt + Hash which is what I thought that URL was trying to explain.
ALTER PROCEDURE [dbo].[HealthCourses_InsertUsers]

(
@c_id int,
@bldg_id int,
@s_id int,
@users_flname varchar(50),
@users_street_address varchar(50),
@users_city varchar(50),
@users_zip varchar(5),
@users_phone varchar(12),
@users_work_ext varchar(4),
@users_email varchar(100),
@users_username varchar(50),
@users_password varchar(50),
@users_password_salt varchar(50)
)

AS

INSERT dbo.HealthCourses_Users (c_id, bldg_id, s_id, users_flname, users_street_address, users_city, users_zip, users_phone, users_work_ext, users_email, users_username, users_password, users_password_salt)

VALUES (@c_id, @bldg_id, @s_id, @users_flname, @users_street_address, @users_city, @users_zip, @users_phone, @users_work_ext, @users_email, @users_username, @users_password, @users_password_salt)

Open in new window

Avatar of Brian

ASKER

@yv989c,

Once again thank you so much for willing to help. I have been seeing different things on the net when I google Salt + Hash passwords but the URL that I originally posted makes the most sense just not sure how it compares to how I did it nor I'm not sure how to implement what the URL mentions into my code.
Avatar of Brian

ASKER

Sorry, also do I need to store the SALT + HASH into the same field into the DB, or keep the SALT + HASH in two separate fields in the DB?

For example:
users_password  varchar(50)  <--- HASH data goes here.
users_password_salt  varchar(50)  <--- SALT for Password goes here.

Also, am I using varchar or should I be using a different field type?
Hi asp_net2, first I want to know that you understand why you are implementing user password hashing in your application, it is only to protect your user password in case that your DB is compromised, if you hash it with the correct technique will be very hard for an attacker to retrieve it.

About your last question, you need to have your password hash in a field, and the data that you will use as salt wherever you want, the better way to explain this is with an example:
// This common function will generate your password plus added salt.
string GetMyPasswordAndSaltData(int userId, string password)
{
    return userId + "MyP@$sw0rd" + newPassword + userId + "AnotherS@alt";
}

// To save your hashed user password
void SetPassword(string userName, string newPassword)
{
    // Get the internal id of userName (GetMyUserId is a sample method), I going to use this info as part of my salt, it is important that the data you will use as part of your salt is not public.
    int userId = GetMyUserId(userName);

    // Create instance of SHA512CryptoServiceProvider, it will generate your hash
    SHA512CryptoServiceProvider SHA512Hash = new SHA512CryptoServiceProvider();

    // Get my salted password
    string myPasswordPlusSalt = GetMyPasswordAndSaltData(userId, newPassword);

    // Use the SHA512Hash algorithm to hash my salted password
    byte[] myPasswordHash = SHA512Hash.ComputeHash(System.Text.Encoding.UTF8.GetBytes(myPasswordPlusSalt));

    // Save my salted password hash to DB (SaveMyHashedPasswordToDb is a sample method)
    SaveMyHashedPasswordToDb(userId, myPasswordHash);
}

// You can use this function to authenticate your user name and password
bool IsValidPassword(string userName, string password)
{
    // Get the internal id of userName (GetMyUserId is a sample method)
    int userId = GetMyUserId(userName);

    // Get the salted password
    string myPasswordPlusSalt = GetMyPasswordAndSaltData(userId, password);

    // Use the SHA512Hash algorithm to hash the salted password
    byte[] myPasswordHash = SHA512Hash.ComputeHash(System.Text.Encoding.UTF8.GetBytes(myPasswordPlusSalt));

    // Retrieve the hash saved in your DB for that user (GetMySavedHashFromDb is a sample method).
    byte[] mySavedPasswordHash = GetMySavedHashFromDb(userId);

    // If both arrays are equals then the password match your saved password, the function will return true.
    return BitConverter.ToString(mySavedPasswordHash) == BitConverter.ToString(myPasswordHash);
}

Open in new window


I have wrote the previous code on the fly so it can contain typos, is just to help you to understand better how to work with user passwords and hashing.

Also, am I using varchar or should I be using a different field type?
You hash is a byte array, so the correct way to save it in your DB is with the binary sql data type.

Now tell me, what represent the column c_id in your HealthCourses_Users table? that is your unique user id?
Avatar of Brian

ASKER

Hi yv989c,

Ok, can you elaborate with the URL that I supplied. I would like to use that technique instead of it's ok. Would you be able to assist with that? Also, is there something I did wrong and if so what in my original code?

Also, should I be using VarBinary() and if so what size should I assign to it, 64, 128, 256?
Hello, ok no problem, right now I'm busy, I will able to give you a hand at the end of day, ok?
Avatar of Brian

ASKER

Hi yv989c,

Ok, that would be great. Thank you so much!!! Like I said before what I originally did works fine but I'm not sure if it's the correct way to SALT + HASH. So I googled SALT + HASH Passwords and found that article. By the sounds of that article it makes sense to me which is why I would like to implement what that article is referring to but not sure how :(

Thanks again yv989c, can't wait to see what you come up with later.

thanks in advance!!!
Hello asp_net2, I have done some fixes and modified your code to use the example showed in the article that you have provided, first the DB objects (TSQL):

Update your [dbo].[HealthCourses_InsertUsers] SP with this:
ALTER PROCEDURE
	[dbo].[HealthCourses_InsertUsers]
(
	@c_id int,
	@bldg_id int,
	@s_id int,
	@users_flname varchar(50),
	@users_street_address varchar(50),
	@users_city varchar(50),
	@users_zip varchar(5),
	@users_phone varchar(12),
	@users_work_ext varchar(4),
	@users_email varchar(100),
	@users_username varchar(50),
	@users_password varchar(128)
)
AS

INSERT
	dbo.HealthCourses_Users
(
	c_id,
	bldg_id,
	s_id,
	users_flname, 
	users_street_address, 
	users_city, 
	users_zip, 
	users_phone, 
	users_work_ext, 
	users_email, 
	users_username, 
	users_password--< YOU MUST CHANGE IT IN YOUR TABLE TO VARCHAR(128)
	-- [users_password_salt] <-- YOU MUST DROP THIS COLUMN
)
VALUES
(
	@c_id,
	@bldg_id, 
	@s_id, 
	@users_flname, 
	@users_street_address, 
	@users_city, 
	@users_zip, 
	@users_phone, 
	@users_work_ext, 
	@users_email, 
	@users_username, 
	@users_password
)

Open in new window


Create this new SP:
CREATE PROCEDURE
	[dbo].[HealthCourses_GetUserPassword]
(
	@users_username varchar(50)
)
AS

SELECT
	users_password
FROM
	dbo.HealthCourses_Users
WHERE
	users_username = @users_username

Open in new window



Now the C# code, this contain the PasswordHash class in the article, your posted method and the new IsValidPassword method to help you authenticate your users:
protected void btn_NewUser_Click(object sender, EventArgs e)
{
    using (SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["HealthCourses"].ConnectionString))
    {
        SqlCommand cmd = new SqlCommand();
        cmd.CommandText = "dbo.HealthCourses_InsertUsers";
        cmd.CommandType = CommandType.StoredProcedure;
        cmd.Connection = conn;

        cmd.Parameters.Add("@c_id", SqlDbType.Int).Value = ddlClient.SelectedItem.Value;
        cmd.Parameters.Add("@bldg_id", SqlDbType.Int).Value = ddlBldgLocation.SelectedItem.Value;
        cmd.Parameters.Add("@s_id", SqlDbType.Int).Value = ddlState.SelectedItem.Value;
        cmd.Parameters.Add("@users_flname", SqlDbType.VarChar, 50).Value = txtName.Text;
        cmd.Parameters.Add("@users_street_address", SqlDbType.VarChar, 50).Value = txtHomeAddress.Text;
        cmd.Parameters.Add("@users_city", SqlDbType.VarChar, 50).Value = txtCity.Text;
        cmd.Parameters.Add("@users_zip", SqlDbType.VarChar, 5).Value = txtZipCode.Text;
        cmd.Parameters.Add("@users_phone", SqlDbType.VarChar, 12).Value = txtHomePhone.Text;
        cmd.Parameters.Add("@users_work_ext", SqlDbType.VarChar, 4).Value = txtWorkExtension.Text;
        cmd.Parameters.Add("@users_email", SqlDbType.VarChar, 100).Value = txtEmailAddress.Text;
        cmd.Parameters.Add("@users_username", SqlDbType.VarChar, 50).Value = txtUserName.Text;

        // Be sure to validate the password rules first (length, complexity, etc.), this code as is allow blank passwords.
        // Also, your column [users_password] must be of type char(128) or varchar(128) and your SP parameter too.
        cmd.Parameters.Add("@users_password", SqlDbType.VarChar, 128).Value = PasswordHash.HashPassword(txtPassword.Text);

        // You no longer need the [users_password_salt] column in your dbo.HealthCourses_Users table, because the PasswordHash class handle it automaticaly in a single string (salt + hash).
        // cmd.Parameters.Add("@users_password_salt", SqlDbType.VarChar).Value = guid;

        try
        {
            conn.Open();

            cmd.ExecuteNonQuery();

            Response.Redirect("newuser_success.aspx");
        }
        catch (Exception ex)
        {
            btn_NewUser.Enabled = true;
            lblInsertError.Text = ex.Message.ToString();
        }
        finally
        {
            conn.Close();
        }
    }
}


// Method to check your user credentials.
private bool IsValidPassword(string userName, string password)
{
    string correctHash = null;
    using (SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["HealthCourses"].ConnectionString))
    {
        SqlCommand cm = new SqlCommand("[dbo].[HealthCourses_GetUserPassword]", conn);
        cm.CommandType = CommandType.StoredProcedure;
        cm.Parameters.Add("@users_username", SqlDbType.VarChar, 50).Value = userName;
        conn.Open();
        correctHash = cm.ExecuteScalar() as string;
    }

    if (correctHash == null)
    {
        // User not found.
        return false;
    }
    else
    {
        return PasswordHash.ValidatePassword(password, correctHash);
    }
}



/*
    * PasswordHash - A salted password hashing library
    * WWW: https://defuse.ca/
    * Use:
    *      Use 'HashPassword' to create the initial hash, store that in your DB
    *      Then use 'ValidatePassword' with the hash from the DB to verify a password
    *      NOTE: Salting happens automatically, there is no need for a separate salt field in the DB
    */
class PasswordHash
{
    /// <summary>
    /// Hashes a password
    /// </summary>
    /// <param name="password">The password to hash</param>
    /// <returns>The hashed password as a 128 character hex string</returns>
    public static string HashPassword(string password)
    {
        string salt = GetRandomSalt();
        string hash = Sha256Hex(salt + password);
        return salt + hash;
    }

    /// <summary>
    /// Validates a password
    /// </summary>
    /// <param name="password">The password to test</param>
    /// <param name="correctHash">The hash of the correct password</param>
    /// <returns>True if password is the correct password, false otherwise</returns>
    public static bool ValidatePassword(string password, string correctHash)
    {
        if (correctHash.Length < 128)
            throw new ArgumentException("correctHash must be 128 hex characters!");
        string salt = correctHash.Substring(0, 64);
        string validHash = correctHash.Substring(64, 64);
        string passHash = Sha256Hex(salt + password);
        return string.Compare(validHash, passHash) == 0;
    }

    //returns the SHA256 hash of a string, formatted in hex
    private static string Sha256Hex(string toHash)
    {
        SHA256Managed hash = new SHA256Managed();
        byte[] utf8 = UTF8Encoding.UTF8.GetBytes(toHash);
        return BytesToHex(hash.ComputeHash(utf8));
    }

    //Returns a random 64 character hex string (256 bits)
    private static string GetRandomSalt()
    {
        RNGCryptoServiceProvider random = new RNGCryptoServiceProvider();
        byte[] salt = new byte[32]; //256 bits
        random.GetBytes(salt);
        return BytesToHex(salt);
    }

    //Converts a byte array to a hex string
    private static string BytesToHex(byte[] toConvert)
    {
        StringBuilder s = new StringBuilder(toConvert.Length * 2);
        foreach (byte b in toConvert)
        {
            s.Append(b.ToString("x2"));
        }
        return s.ToString();
    }
}

Open in new window


Also I have noted that you have created your parameters in a wrong way, the AddWithValue method only need the parameters name and the value (but you were passing a SqlDbType enum as value), I recommend you to use the technique that I show you here to create your parameters in the future.

See the code, test it and give me your thoughts.

I hope this help.
Avatar of Brian

ASKER

LOL, are you the owner of the site you attached in your post? If so, then I'm truly impressed. Also, we must use the same laptop ASUS G51? That's what I use in the office, however, looking into HP EliteBook now, long story :)

Anyway, back to the post :) I made all adjustments like you mentiond but not sure how to implement the following code I have attached. All other changes have been made but I cannot test yet until you stear me into the direction to getting the other code going :(

I tried to create a Class.cs and named it PasswordHash but I must have gotten mixed up with those pesky curly brackets because I had a bunch of red line near them :)

Also, I'm a little confused on what you did if you would not mind explaining as if you where to a 5th grader so that I understand :) Still very new to cryptography. Also, did you look at what I did? If so, what did you think about what I did? Would it be okay to use?
/*
    * PasswordHash - A salted password hashing library
    * WWW: https://defuse.ca/
    * Use:
    *      Use 'HashPassword' to create the initial hash, store that in your DB
    *      Then use 'ValidatePassword' with the hash from the DB to verify a password
    *      NOTE: Salting happens automatically, there is no need for a separate salt field in the DB
    */
class PasswordHash
{
    /// <summary>
    /// Hashes a password
    /// </summary>
    /// <param name="password">The password to hash</param>
    /// <returns>The hashed password as a 128 character hex string</returns>
    public static string HashPassword(string password)
    {
        string salt = GetRandomSalt();
        string hash = Sha256Hex(salt + password);
        return salt + hash;
    }

    /// <summary>
    /// Validates a password
    /// </summary>
    /// <param name="password">The password to test</param>
    /// <param name="correctHash">The hash of the correct password</param>
    /// <returns>True if password is the correct password, false otherwise</returns>
    public static bool ValidatePassword(string password, string correctHash)
    {
        if (correctHash.Length < 128)
            throw new ArgumentException("correctHash must be 128 hex characters!");
        string salt = correctHash.Substring(0, 64);
        string validHash = correctHash.Substring(64, 64);
        string passHash = Sha256Hex(salt + password);
        return string.Compare(validHash, passHash) == 0;
    }

    //returns the SHA256 hash of a string, formatted in hex
    private static string Sha256Hex(string toHash)
    {
        SHA256Managed hash = new SHA256Managed();
        byte[] utf8 = UTF8Encoding.UTF8.GetBytes(toHash);
        return BytesToHex(hash.ComputeHash(utf8));
    }

    //Returns a random 64 character hex string (256 bits)
    private static string GetRandomSalt()
    {
        RNGCryptoServiceProvider random = new RNGCryptoServiceProvider();
        byte[] salt = new byte[32]; //256 bits
        random.GetBytes(salt);
        return BytesToHex(salt);
    }

    //Converts a byte array to a hex string
    private static string BytesToHex(byte[] toConvert)
    {
        StringBuilder s = new StringBuilder(toConvert.Length * 2);
        foreach (byte b in toConvert)
        {
            s.Append(b.ToString("x2"));
        }
        return s.ToString();
    }
}

Open in new window

Avatar of Brian

ASKER

Hi yv989c,

Ok, sorry, I was able to get everything you posted above to work. But only small problem, well, I guess it's a big problem, I can't login now :) :) I'm not sure how to implement your IsValidPassword Event Handler Code with what I'm currently using now. Also, I'm using FormsAuthentication if that matters.

Once again, now sure how to make your code below work with what I have attached.

// Method to check your user credentials.
private bool IsValidPassword(string userName, string password)
{
    string correctHash = null;
    using (SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["HealthCourses"].ConnectionString))
    {
        SqlCommand cm = new SqlCommand("[dbo].[HealthCourses_GetUserPassword]", conn);
        cm.CommandType = CommandType.StoredProcedure;
        cm.Parameters.Add("@users_username", SqlDbType.VarChar, 50).Value = userName;
        conn.Open();
        correctHash = cm.ExecuteScalar() as string;
    }

    if (correctHash == null)
    {
        // User not found.
        return false;
    }
    else
    {
        return PasswordHash.ValidatePassword(password, correctHash);
    }
}
CODEBEHIND:

protected void btn_ProgramInfoSignIn_Click(object sender, EventArgs e)
    {
        //Retrieve the guid from db
        string guid = String.Empty;

        using (SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["HealthCourses"].ConnectionString))
        {
            SqlCommand cmd = new SqlCommand();
            cmd.CommandText = "HealthCourses_LoginPassSalt";
            cmd.CommandType = CommandType.StoredProcedure;
            cmd.Connection = conn;

            cmd.Parameters.AddWithValue("@users_username", SqlDbType.VarChar).Value = txtUserName.Text;

            DataTable dtGuid = new DataTable();

            SqlDataAdapter adp = new SqlDataAdapter();
            adp.SelectCommand = cmd;
            adp.Fill(dtGuid);

            if (dtGuid != null && dtGuid.Rows.Count > 0)
            {
                guid = dtGuid.Rows[0]["users_password_salt"].ToString();

                SqlCommand cmdClientLogin = new SqlCommand();
                cmdClientLogin.CommandText = "HealthCourses_Login";
                cmdClientLogin.CommandType = CommandType.StoredProcedure;
                cmdClientLogin.Connection = conn;

                //Create instance of SHA512CryptoServiceProvider
                SHA512CryptoServiceProvider SHA512Hash = new SHA512CryptoServiceProvider();
                byte[] hashedBytes = null;
                UTF8Encoding encoder = new UTF8Encoding();
                hashedBytes = SHA512Hash.ComputeHash(encoder.GetBytes(txtPassword.Text + guid));

                cmdClientLogin.Parameters.AddWithValue("@users_username", SqlDbType.VarChar).Value = txtUserName.Text;
                cmdClientLogin.Parameters.AddWithValue("@users_password", SqlDbType.VarChar).Value = hashedBytes;

                conn.Open();

                SqlDataReader rdr = cmdClientLogin.ExecuteReader();

                if (rdr.HasRows && rdr.Read())
                {
                    rdr.Close();
                    conn.Close();
                    Session["UserNameSessionID"] = txtUserName.Text;
                    FormsAuthentication.RedirectFromLoginPage(txtPassword.Text, false);
                }
            }

            else
            {
                lblSignInError.Text = "Invalid Credentials!";
            }
        }
    }

Open in new window

Hello buddy, no problem, use this in your authentication page:
// Method to check your user credentials.
private bool IsValidPassword(string userName, string password)
{
    string correctHash = null;
    using (SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["HealthCourses"].ConnectionString))
    {
        SqlCommand cm = new SqlCommand("[dbo].[HealthCourses_GetUserPassword]", conn);
        cm.CommandType = CommandType.StoredProcedure;
        cm.Parameters.Add("@users_username", SqlDbType.VarChar, 50).Value = userName;
        conn.Open();
        correctHash = cm.ExecuteScalar() as string;
    }

    if (correctHash == null)
    {
        // User not found.
        return false;
    }
    else
    {
        return PasswordHash.ValidatePassword(password, correctHash);
    }
}

// Your sign in method
protected void btn_ProgramInfoSignIn_Click(object sender, EventArgs e)
{
    if (IsValidPassword(txtUserName.Text, txtPassword.Text))
    {
        Session["UserNameSessionID"] = txtUserName.Text;
        FormsAuthentication.RedirectFromLoginPage(txtUserName.Text, false);
    }
    else
    {
        lblSignInError.Text = "Invalid Credentials!";
    }
}

Open in new window


Also be sure to put the PasswordHash class file in your aspnet App_Code folder, so it can be accessed by all your pages, and dont forget to create the new SP HealthCourses_GetUserPassword in your DB.
Hey and stop using AddWithValue method for your parameters, use the technique that I show you here, always be explicit:
cmd.Parameters.Add("@users_username", SqlDbType.VarChar, 50).Value = txtUserName.Tex;

Open in new window


But if you still want to use the AddWithValue method, do it this way:

Correct:
cmd.Parameters.AddWithValue("@users_username", txtUserName.Tex);

Open in new window


Wrong:
cmd.Parameters.AddWithValue("@users_username", SqlDbType.VarChar).Value = txtUserName.Text;

Open in new window


Reference:
http://msdn.microsoft.com/en-us/library/system.data.sqlclient.sqlparametercollection.addwithvalue.aspx
Avatar of Brian

ASKER

Hi yv989c,

Ok, I believe I made all the changes correctly for the login page but when I try to login with either correct or not correct credentials I keep getting the following error message below. I'm going to attach my CodeBehind and SP for the Login page.

ERROR IS FROM THE PasswordHash.cs file:
string salt = correctHash.Substring(0, 64);

ERROR MESSAGE:
correctHash must be 128 hex characters!
PasswordHash.cs:

using System;
using System.Security.Cryptography;
using System.Text;

/// <summary>
/// Summary description for PasswordHash
/// </summary>
public class PasswordHash
{
    /// <summary>
    /// Hashes a password
    /// </summary>
    /// <param name="password">The password to hash</param>
    /// <returns>The hashed password as a 128 character hex string</returns>
    public static string HashPassword(string password)
    {
        string salt = GetRandomSalt();
        string hash = Sha256Hex(salt + password);
        return salt + hash;
    }

    /// <summary>
    /// Validates a password
    /// </summary>
    /// <param name="password">The password to test</param>
    /// <param name="correctHash">The hash of the correct password</param>
    /// <returns>True if password is the correct password, false otherwise</returns>
    public static bool ValidatePassword(string password, string correctHash)
    {
        if (correctHash.Length < 128)
            throw new ArgumentException("correctHash must be 128 hex characters!");
        string salt = correctHash.Substring(0, 64);
        string validHash = correctHash.Substring(64, 64);
        string passHash = Sha256Hex(salt + password);
        return string.Compare(validHash, passHash) == 0;
    }

    //returns the SHA256 hash of a string, formatted in hex
    private static string Sha256Hex(string toHash)
    {
        SHA256Managed hash = new SHA256Managed();
        byte[] utf8 = UTF8Encoding.UTF8.GetBytes(toHash);
        return BytesToHex(hash.ComputeHash(utf8));
    }

    //Returns a random 64 character hex string (256 bits)
    private static string GetRandomSalt()
    {
        RNGCryptoServiceProvider random = new RNGCryptoServiceProvider();
        byte[] salt = new byte[32]; //256 bits
        random.GetBytes(salt);
        return BytesToHex(salt);
    }

    //Converts a byte array to a hex string
    private static string BytesToHex(byte[] toConvert)
    {
        StringBuilder s = new StringBuilder(toConvert.Length * 2);
        foreach (byte b in toConvert)
        {
            s.Append(b.ToString("x2"));
        }
        return s.ToString();
    }
}

Open in new window

LOGIN CODEBEHIND:

// Method to check your user credentials.
    private bool IsValidPassword(string userName, string password)
    {
        string correctHash = null;
        using (SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["HealthCourses"].ConnectionString))
        {
            SqlCommand cm = new SqlCommand("HealthCourses_GetUserPassword", conn);
            cm.CommandType = CommandType.StoredProcedure;
            cm.Parameters.Add("@users_username", SqlDbType.VarChar, 50).Value = userName;
            conn.Open();
            correctHash = cm.ExecuteScalar() as string;
        }

        if (correctHash == null)
        {
            // User not found.
            return false;
        }
        else
        {
            return PasswordHash.ValidatePassword(password, correctHash);
        }
    }

    protected void btn_ProgramInfoSignIn_Click(object sender, EventArgs e)
    {
        if (IsValidPassword(txtUserName.Text, txtPassword.Text))
        {
            Session["UserNameSessionID"] = txtUserName.Text;
            FormsAuthentication.RedirectFromLoginPage(txtUserName.Text, false);
        }
        else
        {
            lblSignInError.Text = "Invalid Credentials!";
        }
    }
}

Open in new window

STORED PROCEDURE:

ALTER PROCEDURE [dbo].[HealthCourses_GetUserPassword]
(
	@users_username varchar(50)
)
AS

SELECT
	users_password
FROM
	dbo.HealthCourses_Users
WHERE
	users_username = @users_username

Open in new window

Hello buddy, did you changed the [users_password] column in your table [HealthCourses_Users] to varchar(128) as I indicated?
Avatar of Brian

ASKER

Yes
Ok, I forget to mention that you will need to reset the password for all the existent users by using the new code.
Test the code with a new user to see if everything work as expected.
Avatar of Brian

ASKER

Oh, shoot. I'm very sorry yv989c. I forgot to change users_password from 50 to 128. Sorry, it's workig fine now :)

Also, is it possible to crack this method? Not that I want to but just curious if an experience hacker could crack it.
Of course yes, really I don't like that example, it is handy but all the secrets required to crack your password are saved in your table, if your DB is compromised an experience hacker can easily crack it... Do you want a demo? to make it simple, set a password with only one character A-Z, then send me back your password hash, I will tell you what is your password ;)
Oh, but now I see that it can be easily modified to make it very hard to crack
Avatar of Brian

ASKER

Hi yv989c,

I created a password with one character and the reset are numeric. Good luck :)

I would love a demo that would show how to do that :)

Also, what did you mean by "Oh, but now I see that it can be easily modified to make it very hard to crack "?

Password to crack :)
ee5f62975c99626ad223940314534fb7cc6fd8c5f91a81f64b72c601ca4d9fc3739a0db9402c54a2f424b9a5d2d906fc7797ffa2c026c46df7fd8f2ace57bb26
Hello, I dont understand when you say "and the reset are numeric.", to find it quick I only need one character.
Avatar of Brian

ASKER

:) sorry the first character is a capital J
I have explained wrong, to decode it fast I need a hash of a password that is formed by only one letter :p I want to show you that if I got the salt I can try to decode your password by using a simple dictionary attack.
Avatar of Brian

ASKER

How do I just get you a HASH?
J9090 ;)
Avatar of Brian

ASKER

Your freaking amazing..... How the hell did you do that ???? You are a Wizard :)

I thought this was susspose to be secure :(
Use this improved PasswordHash class and you not have to worry about that:
class PasswordHash
{
    /// <summary>
    /// Hashes a password
    /// </summary>
    /// <param name="password">The password to hash</param>
    /// <returns>The hashed password as a 128 character hex string</returns>
    public static string HashPassword(string password)
    {
        string salt = GetRandomSalt();
        string hash = Sha256Hex(salt + SecretSalt(password));
        return salt + hash;
    }

    // Secret salt, if your DB is compromised, a hacker will need this info to be able to decode your password.
    private static string SecretSalt(string password)
    {
        // Change the text bellow to something that only you will know, otherwise I will be able to decode your password again ;)
        return "ThisWillB3UAn0th3r$3cret" + password + "Sav3d1nY0urPasswordHashCla$$";
    }

    /// <summary>
    /// Validates a password
    /// </summary>
    /// <param name="password">The password to test</param>
    /// <param name="correctHash">The hash of the correct password</param>
    /// <returns>True if password is the correct password, false otherwise</returns>
    public static bool ValidatePassword(string password, string correctHash)
    {
        if (correctHash.Length < 128)
            throw new ArgumentException("correctHash must be 128 hex characters!");

        string salt = correctHash.Substring(0, 64);
        string validHash = correctHash.Substring(64, 64);
        string passHash = Sha256Hex(salt + SecretSalt(password));

        return string.Compare(validHash, passHash) == 0;
    }

    //returns the SHA256 hash of a string, formatted in hex
    private static string Sha256Hex(string toHash)
    {
        SHA256Managed hash = new SHA256Managed();
        byte[] utf8 = UTF8Encoding.UTF8.GetBytes(toHash);
        return BytesToHex(hash.ComputeHash(utf8));
    }

    //Returns a random 64 character hex string (256 bits)
    private static string GetRandomSalt()
    {
        RNGCryptoServiceProvider random = new RNGCryptoServiceProvider();
        byte[] salt = new byte[32]; //256 bits
        random.GetBytes(salt);
        return BytesToHex(salt);
    }

    //Converts a byte array to a hex string
    private static string BytesToHex(byte[] toConvert)
    {
        StringBuilder s = new StringBuilder(toConvert.Length * 2);
        foreach (byte b in toConvert)
        {
            s.Append(b.ToString("x2"));
        }
        return s.ToString();
    }
}

Open in new window


As you can see I have created this method:
// Secret salt, if your DB is compromised, a hacker will need this info to be able to decode your password.
private static string SecretSalt(string password)
{
    // Change the text bellow to something that only you will know, otherwise I will be able to decode your password again ;)
    return "ThisWillB3UAn0th3r$3cret" + password + "Sav3d1nY0urPasswordHashCla$$";
}

Open in new window


The only way (but theoretically is not impossible) that somebody can decode a user password stored in your db is that both systems are compromised (db and web). Also window has places where you can store your secret salt but it will require more work.
Avatar of Brian

ASKER

What is the most secure method if you where storing top secret information. I'm a little worried that what we are doing is not as secure as I thought was. What is more secure than what we are doing now? If there is something better than I need that. Pretend as if we are the existence of alien life :)

Also, how do you know so much about this stuff, I would love to learn and know as much as you and would live to know what material you are studying . People who work with you are very lucky!!!

So is there a more top secret approach to storing data without the worry of your data being decoded? If so I would be willing to create a new post just for that.

Also, why do I need to create a password like you are mentioning above? Where will that be stored at, and how much more secure does that make it?
Wow, there are too many questions, you can trust in the last code that I have posted, it is secure because not all the data required to verify the hash is stored in your DB, so it will be very difficult for an attacker to use a brute force or dictionary method to decode your password, will need years and much computing power to achieve that, but is important that your secret salt is long enough and also stored in a safe place.

The PasswordHash class as is in the article is not safe, first because that code is public, so an experienced person can guest that you are using it and he can see how it works, second because all the data required to decode your password is stored in your db, but adding the simple SecretSalt method to it do a big deference.

BTW I don't know something 100% secure, I say, if it is man made, then another man can crack it ;)

English is not my first language, so I hope you understand my grammatical errors.
Avatar of Brian

ASKER

Ok, what about changing to Sha512 and using a higher field value than 128?

Also, what about storing the salt and hash in two separate fields in the DB like I had initially. Is that more or less secure than storing salt and hash in same DB field?

Also, what about using GUID. Is that less secure to use for generating the salt?

Sorry for so many questions.

No need to apologize for your grammar, you are fine.

Thank you for helping. Wish I had half of your knowledge. I'm sure your coworkers feel blessed to work with someone like you.
Avatar of Brian

ASKER

Also, what happens if I change the SecretSalt after I have used it to create passwords? If I change the SecretSalt every 6 months then what happens when I authenticate against the passwords that I used when using the previous SecretSalt?
1. If you use the SHA512Managed instead of SHA256Managed is better, that will require more CPU time to process so the time required to decrypt a password is also increased, but of course it require more space in your DB (the field size need to be increased), the PasswordHash class need to be modified to work with the SHA512Managed class, also I prefer to save this data in the DB as bytes instead of strings.

2. Is the same thing, indeed, separating values will make things easier for a hacker.

3. Using the RNGCryptoServiceProvider to build your salt is a better technique (it give you more complexity in your salt) that using a guid.
If you change your secret salt all yours passwords need to be recreated, is not a good idea, instead enforce a policy to change your users passwords from time to time.
Avatar of Brian

ASKER

Ok, if I use Sha512 then what field size should I use other than 128?

Also, if you where asked to create a secure siite to store passwords than what would you do with the SecretSalt? Would you never change it or would you change it in time?
I'm busy right now... I will try to respond at night....
Avatar of Brian

ASKER

Hi yv989c,

Ok, thanks. I appreciate your help...
Avatar of Brian

ASKER

Hi yv989c,

When you have time could you show me what I would need to have for Step 1 for your comment 37047811?

I would like to use SHA512 and would like to use bytes rather than strings as you mentioned it being better to store in DB. Also, would it be possible to explain how you crackey my password.

Also, I do plan on implementing the SecretSalt as you showed if you don't mind mentioning that again if you could repost the code I need based on Step 1 from your comment 37047811.

I think that you mentioned a very good point in regards to setting up a policy to have users change passwords. That is very good idea, perhaps I can create another post if you would like to help me with that as well.

Thanks in advance!!!
Hello asp_net2, I have been busy, but I will help you with this today for sure.
Avatar of Brian

ASKER

Hi yv989c,

Ok, thank you. Please take care of what you need to do before helping me with this. I just really appreciate your help and don't want to bother you if your busy. I will wait until you have time. Thank you again!!!
ASKER CERTIFIED SOLUTION
Avatar of Carlos Villegas
Carlos Villegas
Flag of United States of America image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Avatar of Brian

ASKER

Hi yv989c,

Thank you, that all worked out perfectly. I'm getting ready to close this post. But first, could you explain how you cracked that password Hash and how I can best protect this code and the values in the DB?

Also, I would like to create another post about password policy but would really like you help with that since you already know my DB schema and have a understanding of my code. If you can't help I understand and perhaps you can point me to some good tutoials that explain how to setup password policy using ASP.NET C# if you can't help. It would be nice to have user change his/her password every 90 days or whatever.
Hello again buddy, I was able to easily guess your password because this:
1. I know the code that you have used to hash it.
2. The hash that you provided has the salt in it.
3. You give me a hint about your password, start with the J letter and the rest are numbers, that info help me to do a brute force crack ;)

Here the code that I wrote to guess your password:
void FindPassword()
{
    string seed = "J";
    string pwd = seed;
    int counter = 0;
    while (!PasswordHash.ValidatePassword(pwd, "ee5f62975c99626ad223940314534fb7cc6fd8c5f91a81f64b72c601ca4d9fc3739a0db9402c54a2f424b9a5d2d906fc7797ffa2c026c46df7fd8f2ace57bb26"))
    {
        pwd = seed + counter.ToString();
        counter++;
    }
    Console.WriteLine("Your password is: " + pwd);
}

Open in new window


But what happen if you use a secret salt too? well is going to be almost impossible to me to crack your password, by using my secret salt example (of course you need to change it to something that I dont know) is like I try to guess a 50 char length password with especial characters, I will need tooo many years to crack your password by using the computer power that I have on hand.

About protecting your code..., that is a complex topic, there are many variables, your OS, your environment, your network, etc etc etc... I can't give you a direct answer about that, just keep on mind that your secret (salt) need to be in a safe place, like it is your home keys, if you leave it in a public place, anyone will be able to enter in your house.

If you open a question about password policy's I will participate on it ok.
Avatar of Brian

ASKER

Hi yv989c,

Ok, I have to ask this question, who do you work for, I only ask because you are very smart and you still amazed me of what you did and most importantly how knowledable you are :)

I'm a little confused on how I can store that special password safely if it's part of the PasswordHash.cs code. the .cs file is stored in the App_Code folder but is that secure enough? If not, how do you suggest that I store it in a safe place?

Also, for the password cracking you did is that all I would have to do is add that code above along with the PasswordHash.cs file or is there more to it? What Brute Force software did you use?

>> If you open a question about password policy's I will participate on it ok.
I just created a post, please see the URL for it below.

https://www.experts-exchange.com/questions/27425465/Password-Policy's.html
Hello, about your first paragraph I must say "keep hungry stay foolish".

I'm a little confused on how I can store that special password safely if it's part of the PasswordHash.cs code. the .cs file is stored in the App_Code folder but is that secure enough? If not, how do you suggest that I store it in a safe place?
Well, the App_Code folder is a safe folder, aspnet don't allow to download the content of that folder, unless of course some body exploit an aspnet vulnerability that allow to read its content and then you know the rest... I can suggest to save that info (your secret salt) in a section on your web.config file, then you can encrypt that section, aspnet can automatically decrypt the value stored in your web.config file section when you read it, if your web application is compromised the attacker will have everything but he can't read the content of your web.config file because it is encrypted, and the key required to decrypt that info is the machine key, so ALL your computer must be compromised to allow him to decrypt your salt and then your user passwords, I think this is the best method that you can use, of course this is not the only way.
Reference:
http://msdn.microsoft.com/en-us/library/dtkwfdky.aspx


Also, for the password cracking you did is that all I would have to do is add that code above along with the PasswordHash.cs file or is there more to it? What Brute Force software did you use?
Yes, PasswordHash class + The code provided, why I will require something else? the code just try until there is a match, that is the reason it is called brute force.
Avatar of Brian

ASKER

Hi yv989c,

ok, last question only and I"m closing post :) If you believe or think that it may be better to store the SecretPassword into the web.config file then can you tell me what I need to do? Also, what about adding the SecretPassword to the Salt + Hash, if it's stored in the web.config then how to I access that when I initially create the new password?
asp_net2 .... you are asking too much in this question ;)

If you believe or think that it may be better to store the SecretPassword into the web.config file then can you tell me what I need to do?
I left you a reference: http://msdn.microsoft.com/en-us/library/dtkwfdky.aspx, I can help you with that, I know it is not a simple task if you have never worked with this kind of stuff, but is a good idea to open a new question for this, if I don't have the time I'm sure there is others that can help you with this task.



Also, what about adding the SecretPassword to the Salt + Hash
Bad idea, then stop calling it SecretPassword, remember, you want to hide that secret, if you put it on your DB and then it is compromised you are giving all the info required to crack your password.



if it's stored in the web.config then how to I access that when I initially create the new password?
This is related to the first quote.
Avatar of Brian

ASKER

Hi yv989c,

Thank you so very much for all your time, patience, and willing to help others. I can't thank you enough. I really have a passion for stuff like this and working with people like you is AMAZING...

Thank you again yv989c. Not sure where you work at but I'm sure your fellow employees but enjoy working with you daily. I wish that only one day I can be as knowledgable as you so I can help others and pay it forward...
Glad to have been of help buddy