Benny O'Neill
Benny O'Neill

Reputation: 208

Authenticate Hashed Password

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:

  1. Save the salt in the database or

  2. 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

Answers (1)

Majid Parvin
Majid Parvin

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

Related Questions