Reputation: 208
I'm creating a small C# WinForms application but am having trouble with the verification/authentication of a hashed password.
When the user is created, their password is hashed and stored in the database. I don't know how to compare the entered password (when the user logs in) against the hashed password in the database.
The code to create the hash is below:
private static string CreateHashedPassword(string username, string plainTextPassword)
{
byte[] salt;
new System.Security.Cryptography.RNGCryptoServiceProvider().GetBytes(salt = new byte[16]);
System.Security.Cryptography.Rfc2898DeriveBytes pbkdf2 = new System.Security.Cryptography.Rfc2898DeriveBytes(plainTextPassword, salt, 3000);
byte[] hash = pbkdf2.GetBytes(20);
byte[] hashBytes = new byte[36];
Array.Copy(salt, 0, hashBytes, 0, 16);
Array.Copy(hash, 0, hashBytes, 16, 20);
return Convert.ToBase64String(hashBytes);
}
When the user attempts to log in, I call this method again to get the hashed password to compare against the database, but it never matches.
I'm pretty sure that's because a new salt is created each time the method is run, so I'm pretty sure the way to fix it (but I don't know how) is to either:
Save the salt in the database or
Use something like the username to create the salt. This is the preferred method, but I don't know how to derive a salt from a pre-determined string like a username.
Any thoughts/pointers? Thanks!
Upvotes: 0
Views: 474
Reputation: 4992
It's happened because your hashing way using a random method to generate it.
RNGCryptoServiceProvider Implements a cryptographic
Random Number Generator
(RNG) using the implementation provided by the cryptographic service provider (CSP). This class cannot be inherited.
You can use this method to hash:
public static string GenerateKeyHash(string Password)
{
if (string.IsNullOrEmpty(Password)) return null;
if (Password.Length < 1) return null;
byte[] salt = new byte[20];
byte[] key = new byte[20];
byte[] ret = new byte[40];
try
{
using (RNGCryptoServiceProvider randomBytes = new RNGCryptoServiceProvider())
{
randomBytes.GetBytes(salt);
using (var hashBytes = new Rfc2898DeriveBytes(Password, salt, 10000))
{
key = hashBytes.GetBytes(20);
Buffer.BlockCopy(salt, 0, ret, 0, 20);
Buffer.BlockCopy(key, 0, ret, 20, 20);
}
}
// returns salt/key pair
return Convert.ToBase64String(ret);
}
finally
{
if (salt != null)
Array.Clear(salt, 0, salt.Length);
if (key != null)
Array.Clear(key, 0, key.Length);
if (ret != null)
Array.Clear(ret, 0, ret.Length);
}
}
and this method to compare your passwords:
public static bool ComparePasswords(string PasswordHash, string Password)
{
if (string.IsNullOrEmpty(PasswordHash) || string.IsNullOrEmpty(Password)) return false;
if (PasswordHash.Length < 40 || Password.Length < 1) return false;
byte[] salt = new byte[20];
byte[] key = new byte[20];
byte[] hash = Convert.FromBase64String(PasswordHash);
try
{
Buffer.BlockCopy(hash, 0, salt, 0, 20);
Buffer.BlockCopy(hash, 20, key, 0, 20);
using (var hashBytes = new Rfc2898DeriveBytes(Password, salt, 10000))
{
byte[] newKey = hashBytes.GetBytes(20);
if (newKey != null)
if (newKey.SequenceEqual(key))
return true;
}
return false;
}
finally
{
if (salt != null)
Array.Clear(salt, 0, salt.Length);
if (key != null)
Array.Clear(key, 0, key.Length);
if (hash != null)
Array.Clear(hash, 0, hash.Length);
}
}
Upvotes: 1