Link to home
Start Free TrialLog in
Avatar of atljarman
atljarman

asked on

.net 4.0 password history example sql server

Hi, I am looking for a good example of password history for user management.  Any good samples?  I think I woulg need to create the table, lookup, and salt/hash comparison.
Avatar of btan
btan

There is an old past that you may want to check out, it is .NET but not of latest v4 though
http://www.codeproject.com/Articles/15750/Simple-Password-Manager-Using-System-Security
..another I am thinking if can leverage on open source password mgr such as KeePass which is widely used
http://www.codeproject.com/Articles/5489/KeePass-Password-Safe
Avatar of atljarman

ASKER

Thank you.  I was looking for something more along these lines... http://ronanmoriarty.wordpress.com/2012/03/14/implementing-password-history-using-a-custom-membership-provider/

I'm not sure how to implement this for a forms project or how to use the entity provider.  This appears to be just what I need and I was able to get to the triggers.  After that, I'm stuck.
This might be another approach.  My password history is stored in the database table phistory.  I'm trying to figure out how to add this logic to the backend of my password change page, but if it goes somewhere else that would be good to know.

http://forums.asp.net/t/1184599.aspx?How+to+enforce+password+uniqueness+verify+against+list+of+previous+passwords+

public class ProviderWithPasswordHistory : SqlMembershipProvider
{
protected static PasswordHistoryTableAdapters.T_PasswordHistoryTableAdapter taPasswordHistory;
protected static MembershipUser mu;

public override void Initialize(string name, System.Collections.Specialized.NameValueCollection config)
{
taPasswordHistory = new PasswordHistoryTableAdapters.T_PasswordHistoryTableAdapter();
base.Initialize(name, config);
}
public bool PasswordAlreadyUsed(string pass)
{
mu = Membership.GetUser();
string strPasswordSalt = taPasswordHistory.GetMemberPasswordSalt(Membership.ApplicationName, mu.UserName);
byte[] bIn = Encoding.Unicode.GetBytes(pass);
byte[] bSalt = Convert.FromBase64String(strPasswordSalt);
byte[] bRet = null;
byte[] bAll = new byte[bSalt.Length + bIn.Length];
System.Buffer.BlockCopy(bSalt, 0, bAll, 0, bSalt.Length);
System.Buffer.BlockCopy(bIn, 0, bAll, bSalt.Length, bIn.Length);
bRet = base.EncryptPassword(bAll);
string thepasswd = Convert.ToBase64String(bRet);

//Compare old passwords
DataTable dtPasswordHistory = taPasswordHistory.GetPasswordHistoryForAUser(Membership.ApplicationName, mu.UserName);
ArrayList arPasswordHistory = new ArrayList();
foreach (DataRow dr in dtPasswordHistory.Rows)
{
arPasswordHistory.Add(dr[0].ToString());
}
if (arPasswordHistory.Contains(thepasswd))
{
return true;
}
else
{
return false;
}
}

}
the logic would be
enter new password
check if password is in the history
call password history checker routine
if answer is yes display error message and go back to enter new password
if answer is no execute change password
return

password history
variables needed username and suggested password
get username
for each item in password history {
check if suggested password is equal to password history item if yes return = yes
}
remove oldest password from password history
insert suggested password
return no
you will definitely need a hashed password db where each password entered will be validated against the db and history records (e.g. restrict reuse count and passwd complexity etc). Actually there is a MembershipProvider.ValidatingPassword that can be leveraged upon to do the history check.

Along the line of the SQL membership, there should be some flow to get the intercept for the history check, I looked at forum and it seems to be the below.

In changepasswd.apsx (runat="server")
@ Page_load <script> Add in the method such as ChangepasswdFNC
@ ChangePasswdFORM declared <form> Add in at the button (or targeted trigger) to runat=server the ChangepasswdFNC

Next is to setup the Membership.ValidatingPassword e.g.
Declare a Method for PasswdChgInterceptSetup which has
Membership.ValidatingPassword += Membership_ValidatingPasswordFNC
(This occurs when a user is created, a password is changed, or a password is reset.)

Then inside  Membership_ValidatingPasswordFNC,
Declare the action for the Validation against your DB

I saw this EE posting which may be along the line for the validation and there are some suggested code. You can catch it too.
SOLUTION
Avatar of atljarman
atljarman

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
ASKER CERTIFIED SOLUTION
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
btan,

I am testing the zetetic library you suggested... Sorry I had to go backwards thinking it better to have a more secure password first.  

I'm not sure how to modify the salt so it is unique with each password (as it doesn't appear to change the password just the format of the hashed -possibly encoded - password).  Any ideas how to implement the unique salt.   Also, once I have the new hashed password type and salt, I'm not sure how to make the history work.  Any thoughts.

Thanks for the great suggestions.
I believe the sample link (http://www.obviex.com/samples/hash.aspx) already has stated to use below as example which is much better than hardcoding or grabbing from dictionary - best effort
// Generate a random number for the size of the salt.
            Random  random = new Random();
            int saltSize = random.Next(minSaltSize, maxSaltSize);

            // Allocate a byte array, which will hold the salt.
            saltBytes = new byte[saltSize];

            // Initialize a random number generator.
            RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();

            // Fill the salt with cryptographically strong byte values.
            rng.GetNonZeroBytes(saltBytes); 

Open in new window

The salt is part of computing the hashed password so once that is done, it can be string (e.g. Base64 encoded) compared to the DB for any similar hit. I did not drill into the  Zetetic.Security package but since it is using PKCS#5 such as pbkdf2 it is also leveraging in this same strategy with supplied salt and password.
btan,

I think I'm going to hold off on the salt for the moment and get back to the password history and comparison.  Now that I'm implementing the new package for the hashing should the function on this page http://geekswithblogs.net/Nettuce/archive/2012/06/14/salt-and-hash-a-password-in.net.aspx still allow you to hash the password without modification for comparison to the history of hashes?  I apologize for asking for verification.  I just had a ton a problems trying to match the new password proposed with the previous passwords.
yes it should as the salt is generated still from RNGCryptoServiceProvider
and is based on Rfc2898DeriveBytes which will take the password and salt and iteration counts to eventually create a unique key. thereafter, you can use this key for encryption, or even just encode that in base 62 for storage and comparisons which is done in the posting that I shared and mentioned in your prev post.
Please do not close this question.  I was able to implement the enhanced hashing function but not the hash comparion capability using the form field comparing it to what is in the password history database.  I hope to do that this next week.
Sorry for taking so long on this.  I've been a bit lost.  So It seems that the  Zetetic.Security functionality just replaces the core SHAxx hashing functionality and nothing else.  So in theory, if I could just then use http://geekswithblogs.net/Nettuce/archive/2012/06/14/salt-and-hash-a-password-in.net.aspx to hash the new password suggested by the user and compare it to the database values.  Is that correct?  From the link on this posting that you so kindly pointed out, really is not hashing any new hash type it is using the provider from within the membership provider of ASP .net.  The only thing that is being added (which someone could modify) is the creation of the salt that is also shown on that link.  Please let me know if that makes sense.  I'm going to try and implement this now that I've had some time to allow the thoughts to refresh.
yes they are based on PBDKF2. The geekswith blog implements 1000 rounds iteration of the hash and for Zetetic it implement 5000 round. this deter the work factor of breaking through the hash, it may increased security level but also the performance may be impacted. you can try out then. There are sharing on this in Troy Hunt blog (http://www.troyhunt.com/2012/07/stronger-password-hashing-in-net-with.html) too

From Zetetic, see their sample and article
@ https://www.zetetic.net/blog/2012/3/29/strong-password-hashing-for-aspnet.html
The .NET Framework has included an implementation of Password Based Key Derivation Function 2 (PBKDF2) in the Rfc2898DeriveBytes class, going all the way back to .NET Framework 2.  However, Rfc2898DeriveBytes does not implement the HashAlgorithm method that would make it compatible with the ASP.NET SqlMembershipProvider or with other general-purpose programmatic .NET hashing interfaces.
There is no luck here.  So I tried encoding, hashing, but i just can not get the new password to match what exists in the database.  It would be great to see a working example even if I had to go back to SHA1 or SHA256 (standard for .Net 4.0).
Ok... this does not work.  I was looping through the database for one user with the same salt for with the passwords Test12, Test123, and Test1234.  I can not see what I am doing wrong here.  

    protected void ChangePassword1_ChangingPassword(object sender, LoginCancelEventArgs e)
{
        // Get a reference to the currently logged on user
        MembershipUser currentUser = Membership.GetUser();
        // Determine the currently logged on user's UserId value
        Guid currentUserId = (Guid)currentUser.ProviderUserKey;
        // Assign the currently logged on user's UserId to the @UserId parameter
        String UserId = currentUserId.ToString();

        string strConnection2 = ConfigurationManager.ConnectionStrings["DefaultConnection"].ConnectionString;
 		string queryString = "SELECT * FROM PasswordHistory where UserId='" + UserId + "';";

        byte[] saltInDb = Convert.FromBase64String("Test1234");
        byte[] passWordBuffer = Encoding.Unicode.GetBytes("F7GnpjwvQHJtgkA0ZQVBEA==");
        byte[] completeHash = new byte[saltInDb.Length + passWordBuffer.Length];

        saltInDb.CopyTo(completeHash, 0);
        passWordBuffer.CopyTo(completeHash, saltInDb.Length);

        //byte[] hash2 = HashAlgorithm.Create(Membership.HashAlgorithmType).ComputeHash(completeHash); //pbkdf2_local
        byte[] hash2 = HashAlgorithm.Create("pbkdf2_local").ComputeHash(completeHash); //pbkdf2_local

        /*
        if (passwordRow.Password == Convert.ToBase64String(hash2))
        {
            return true;
        }
        */
        
        

        using (SqlConnection connection =
                   new SqlConnection(strConnection2))
        {
            SqlCommand command = new SqlCommand(queryString, connection);
            connection.Open();

            SqlDataReader reader = command.ExecuteReader();



            // Call Read before accessing data. 
            while (reader.Read())
            {

            	string pws = reader["PasswordSalt"].ToString();
            	string pw = reader["Password"].ToString();


				//string hashOldPassword = FormsAuthentication.HashPasswordForStoringInConfigFile(pw,"SHA1");

                lblmessage.Text = lblmessage.Text + 
                    "<br />PW Stored:" + pw + 
                    "<br />Hash: " + ComputeHash("F7GnpjwvQHJtgkA0ZQVBEA==", "Test134") + 
                    "<br />Encode: " + EncodePassword("F7GnpjwvQHJtgkA0ZQVBEA==", "Test1234") + 
                    "<br />Test: " + Convert.ToBase64String(hash2);
            }

            // Call Close when done reading.
            reader.Close();
        }



    // do your lookup here, 
    bool passwordHasBeenPreviouslyUsed = true;

    if (passwordHasBeenPreviouslyUsed)
    {
        e.Cancel = true;
        // notify of error
                lblmessage.Text = "<p class='error'>" + lblmessage.Text + "   It is too early to reuse that password per security policy.</p>";
        return;
    }

}

    public string EncodePassword(string pass, string salt)
{
	byte[] bytes = Encoding.Unicode.GetBytes(pass);
	byte[] src = Convert.FromBase64String(salt);
	byte[] dst = new byte[src.Length + bytes.Length + 1];
	System.Buffer.BlockCopy(src, 0, dst, 0, src.Length);
	System.Buffer.BlockCopy(bytes, 0, dst, src.Length, bytes.Length);
    //System.Security.Cryptography.HashAlgorithm HashAlg = System.Security.Cryptography.HashAlgorithm.Create("SHA1");
    System.Security.Cryptography.HashAlgorithm HashAlg = System.Security.Cryptography.HashAlgorithm.Create("pbkdf2_local");
    //HashAlgorithm algorithm = HashAlgorithm.Create("pbkdf2_local");
	dynamic inArray = HashAlg.ComputeHash(dst);
	return Convert.ToBase64String(inArray);
}


    static string ComputeHash(string salt, string password)
    {
        var saltBytes = Convert.FromBase64String(salt);
        using (var rfc2898DeriveBytes = new Rfc2898DeriveBytes(password, saltBytes, 1000))
            return Convert.ToBase64String(rfc2898DeriveBytes.GetBytes(256));
    }

Open in new window



Here is the label that is populated on the page:
PW Stored:s6nkHBq9z9k44F6fWi3KBHW67JelsMGH
Hash: S51/CX3yozeba+hAQyz6WubgZtlIO6k7P7jFGe+ZNiWLv+3+6Y3HArvxLX1wFfiIDO4TBZg71qWCxw7rjHc5Z7ZWENkU4jIEyI/7mCIZN+zdQJubjlSwjvTHJ2Ymxy9wkhg2+W6VK+zSzp3YpDZ/LM74hByFkdb7Ln4XLaqiJCEnMc6wGuLW7flcWRZElL0pYub7FY+1SfXk8NRI71h0qwakq4QGgrkPZguBK5sbylUHaeQWK2Dz6hbwKOjub2HAowTbB4vt9pVuxFN1ow4GgW1pM9Tx+TAzZPQJ9ihsiPqh1f8mzWyhEI75HX/ZpssJLElqWXY/rdkaIRosYxRt9Q==
Encode: caKWaWxY28CjOUSH2xmwyJJio+Cg/J7Y
Test: caKWaWxY28CjOUSH2xmwyJJio+Cg/J7Y
PW Stored:RmAR3vDLBYMaPZ9ubnsM5MaFv6IikOBg
Hash: S51/CX3yozeba+hAQyz6WubgZtlIO6k7P7jFGe+ZNiWLv+3+6Y3HArvxLX1wFfiIDO4TBZg71qWCxw7rjHc5Z7ZWENkU4jIEyI/7mCIZN+zdQJubjlSwjvTHJ2Ymxy9wkhg2+W6VK+zSzp3YpDZ/LM74hByFkdb7Ln4XLaqiJCEnMc6wGuLW7flcWRZElL0pYub7FY+1SfXk8NRI71h0qwakq4QGgrkPZguBK5sbylUHaeQWK2Dz6hbwKOjub2HAowTbB4vt9pVuxFN1ow4GgW1pM9Tx+TAzZPQJ9ihsiPqh1f8mzWyhEI75HX/ZpssJLElqWXY/rdkaIRosYxRt9Q==
Encode: caKWaWxY28CjOUSH2xmwyJJio+Cg/J7Y
Test: caKWaWxY28CjOUSH2xmwyJJio+Cg/J7Y
PW Stored:fTDUB4FymYuoEkSxe2A71FC6zDROtCQb
Hash: S51/CX3yozeba+hAQyz6WubgZtlIO6k7P7jFGe+ZNiWLv+3+6Y3HArvxLX1wFfiIDO4TBZg71qWCxw7rjHc5Z7ZWENkU4jIEyI/7mCIZN+zdQJubjlSwjvTHJ2Ymxy9wkhg2+W6VK+zSzp3YpDZ/LM74hByFkdb7Ln4XLaqiJCEnMc6wGuLW7flcWRZElL0pYub7FY+1SfXk8NRI71h0qwakq4QGgrkPZguBK5sbylUHaeQWK2Dz6hbwKOjub2HAowTbB4vt9pVuxFN1ow4GgW1pM9Tx+TAzZPQJ9ihsiPqh1f8mzWyhEI75HX/ZpssJLElqWXY/rdkaIRosYxRt9Q==
Encode: caKWaWxY28CjOUSH2xmwyJJio+Cg/J7Y
Test: caKWaWxY28CjOUSH2xmwyJJio+Cg/J7Y

Open in new window


This is using the zetetic security one line security approach: https://www.zetetic.net/blog/2012/7/3/secure-password-hashing-for-aspnet-in-one-line.html  I can't even tell if this is formatting the password that is stored in the database correctly.
if you are trying to compare with existing password history table, the algorithm (like in your past - "bRet = base.EncryptPassword(bAll);") must be the same and not just the salt per se. even then the salt used in existing password history must be reused and with same algorithm (and padding) to create that match - for encoding it is to ensure the input and output for single entry processing are consistent format and length.
Ok.  I'm using the membership provider to create the hash and salt.  How do you repeat that outside of the provider?   All examples I've tried have failed.
looks like VS 2010 and VS2012 have different, pls see under "Better hashing for young and old" and "Hashes ain’t hashes" section
...old SqlMembershipProvider to the new DefaultMembershipProvider...., Updating a 2010 project template is easy – and potentially catastrophic. The problem is simply that if you have an existing project with existing hashes, following the advice above will fundamentally break your authentication. Even if you migrate all the accounts from the old tables to the new (which is easy enough), the stored hashes from the old days will not match the new hashes from the PBKDF2 model. Nobody will be able to logon.
http://www.troyhunt.com/2012/07/stronger-password-hashing-in-net-with.html

Also it stated
Hashing algorithms always create the same cipher length regardless of the input string; it must mean that something different is going on. And there is – what you’re seeing here is the result of the universal membership provider which is now calling directly into the Crypto.HashPassword method that we’ve had in System.Web.Helpers for a little while now.

One of the great things about significant portions of ASP.NET now being open sourced is that it’s easier than ever to take a look under the covers. For those that want to see what’s going on, the entire crypto implementation is over on CodePlex.
maybe we can check out that Codeplex to see any diff from yours...http://aspnetwebstack.codeplex.com/SourceControl/changeset/view/89b9166ca722#src/System.Web.Helpers/Crypto.cs
So I reverted back to the base hashing.  I added this to the code behind for a label.Text to verify that it was truely SHA1.  I feel like i'm losing my mind her but I think slow persistence will pay off.

Membership.HashAlgorithymType.ToString();

Open in new window

the universal membership provider which is now calling directly into the Crypto.HashPassword method, already stated it is the password hash generated with the RFC 2898 algorithm using a 128-bit salt, a 256-bit subkey, and 1000 iterations. The format of the generated hash bytestream is {0x00, salt, subkey}, which is base-64 encoded before it is returned.

And that hashpassword is found in crypto.cs stated it is PBKDF2 with HMAC-SHA1, 128-bit salt, 256-bit subkey, 1000 iterations too. One key comment to note is it stated hashedPassword must be of the format of HashWithPassword (salt + Hash(salt+input).

We probably want to use that hashpassword function...
See... that's the funny thing.  I ported this over from an ASP .Net 2.0 to 4.0 a couple of years ago.  In the configuration when removing the zetetic security settings, it goes back to SHA1 indicating that it is based on the old model.  Not sure what is going on or how to fix that part.... Right now I did finally... finally... at least be able to create a SHA1 text password with the same salt as the saved password in the database.    I will try to further refine then figure out how to get a better hashing.  Hoping to be able to close this one out soon.
thanks for sharing - as long as it works in first place then thing can be enhance along the way.
thanks for sharing
I had to go back and start over.  I just kept walking through it until it worked.   Thank you for your time and comments.
thanks!