c00000fd
c00000fd

Reputation: 22285

Secure password hashing with salt, but what about storing it in local cookies?

I've recently read an interesting article about securely hashing user passwords by using "salt". (This is the original article, which unfortunately seems to be down at the time of this post, so here's the cached version.)

I totally agree with this concept, except that I can't seem to find a way of securely storing a user login in a local cookie (or session) since the salt + PBKDF2 hash combination is done randomly for each time. To better understand what I mean, let me copy the C# code from the article:

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

namespace PasswordHash
{
    /// <summary>
    /// Salted password hashing with PBKDF2-SHA1.
    /// Author: havoc AT defuse.ca
    /// www: http://crackstation.net/hashing-security.htm
    /// Compatibility: .NET 3.0 and later.
    /// </summary>
    class PasswordHash
    {
        // The following constants may be changed without breaking existing hashes.
        public const int SALT_BYTES = 24;
        public const int HASH_BYTES = 24;
        public const int PBKDF2_ITERATIONS = 1000;

        public const int ITERATION_INDEX = 0;
        public const int SALT_INDEX = 1;
        public const int PBKDF2_INDEX = 2;

        /// <summary>
        /// Creates a salted PBKDF2 hash of the password.
        /// </summary>
        /// <param name="password">The password to hash.</param>
        /// <returns>The hash of the password.</returns>
        public static string CreateHash(string password)
        {
            // Generate a random salt
            RNGCryptoServiceProvider csprng = new RNGCryptoServiceProvider();
            byte[] salt = new byte[SALT_BYTES];
            csprng.GetBytes(salt);

            // Hash the password and encode the parameters
            byte[] hash = PBKDF2(password, salt, PBKDF2_ITERATIONS, HASH_BYTES);
            return PBKDF2_ITERATIONS + ":" +
                Convert.ToBase64String(salt) + ":" +
                Convert.ToBase64String(hash);
        }

        /// <summary>
        /// Validates a password given a hash of the correct one.
        /// </summary>
        /// <param name="password">The password to check.</param>
        /// <param name="goodHash">A hash of the correct password.</param>
        /// <returns>True if the password is correct. False otherwise.</returns>
        public static bool ValidatePassword(string password, string goodHash)
        {
            // Extract the parameters from the hash
            char[] delimiter = { ':' };
            string[] split = goodHash.Split(delimiter);
            int iterations = Int32.Parse(split[ITERATION_INDEX]);
            byte[] salt = Convert.FromBase64String(split[SALT_INDEX]);
            byte[] hash = Convert.FromBase64String(split[PBKDF2_INDEX]);

            byte[] testHash = PBKDF2(password, salt, iterations, hash.Length);
            return SlowEquals(hash, testHash);
        }

        /// <summary>
        /// Compares two byte arrays in length-constant time. This comparison
        /// method is used so that password hashes cannot be extracted from
        /// on-line systems using a timing attack and then attacked off-line.
        /// </summary>
        /// <param name="a">The first byte array.</param>
        /// <param name="b">The second byte array.</param>
        /// <returns>True if both byte arrays are equal. False otherwise.</returns>
        private static bool SlowEquals(byte[] a, byte[] b)
        {
            uint diff = (uint)a.Length ^ (uint)b.Length;
            for (int i = 0; i < a.Length && i < b.Length; i++)
                diff |= (uint)(a[i] ^ b[i]);
            return diff == 0;
        }

        /// <summary>
        /// Computes the PBKDF2-SHA1 hash of a password.
        /// </summary>
        /// <param name="password">The password to hash.</param>
        /// <param name="salt">The salt.</param>
        /// <param name="iterations">The PBKDF2 iteration count.</param>
        /// <param name="outputBytes">The length of the hash to generate, in bytes.</param>
        /// <returns>A hash of the password.</returns>
        private static byte[] PBKDF2(string password, byte[] salt, int iterations, int outputBytes)
        {
            Rfc2898DeriveBytes pbkdf2 = new Rfc2898DeriveBytes(password, salt);
            pbkdf2.IterationCount = iterations;
            return pbkdf2.GetBytes(outputBytes);
        }
    }
}

So as you can see, the only way to validate a password would be to call ValidatePassword with a plain-text password. In my previous implementation of plain SHA1, to store user login in a local browser, I'd put that SHA1 value in a cookie and compare it with the one stored in the database on a server for each page. But how do you do the same with this "secure" approach?

Any ideas?

Upvotes: 3

Views: 4449

Answers (3)

Mike Beeler
Mike Beeler

Reputation: 4101

Hash is stored on server, as part of user authentication when the plaintext password is sent to the server, the computed salt (per user) is stored in a secure database and added to the password and the crypto result computed based on password + hash.

Cookies are the wrong approach, because they expire and there is no reason that the web client needs the salt.

Salting Your Password: Best Practices?

Upvotes: 1

Edwin
Edwin

Reputation: 1468

You don't want to store a hashed password in cookies, because that would be the same thing as storing the password itself in cookies. If the hash is all you need to login, then it is the password. The reason you want to hash your user's password with random salt is not to secure the logon process, but to secure your password table. If an attacker steals your password table, and each password is not hashed with unique salt, it will be easy for him/her to figure out many of the passwords. Always hash your user's password with a unique salt. It's fine to store this salt with the hashed password. If you want a secure way to use hashes to authenticate your user based on data in cookies, you will need to go in the direction of temporary credentials or sessions. the simplest I can think of is something like the following:

  1. When your user logs in with his password, create a 'Session'. Assign a value to uniquely identify this session, store the time (to the millisecond) the session was created, and create a random value as salt.

  2. Hash the session's id with the salt. Save this hash and the session id in user's cookies.

  3. Each time a page is requested perform the hash again and compare it with the value stored in the user's cookies. If the values match and too much time hasn't passed since the session was created, your user can view the page. Otherwise send them to login again with their password.

Upvotes: 5

Ameen
Ameen

Reputation: 2586

The salt of a hash function in general is something that you compute based on a rule. For example, you use the user identifier as the salt itself. You use the "username" value to find the user in the database, and then use the user id as the salt. This way, you don't have to store the salt anywhere and the salt for each hashed value is different.

Upvotes: 0

Related Questions