2

I know there are probably a lot of questions like this already. But, I really haven't found the definite answer for my question.

I know that passwords are stored in the database with a prepended random salt followed by the hashed password. The value of the password is actually never known (by the server and thus the server admins).

What is the standard hashing algorithm? I know cryptography is a dynamic field and changes with time. So I'm asking what's the current industry standard for hashing.

I'm going to be using this for a e-commerce site. So password storage security is actually very important.

Gumbo
  • 643,351
  • 109
  • 780
  • 844
matteeyah
  • 855
  • 11
  • 24

4 Answers4

4

The go-to reference on this topic for a few common languages is https://crackstation.net/hashing-security.htm. I've reproduced the C# version of their code sample below, but other languages are provided

/* 
 * Password Hashing With PBKDF2 (http://crackstation.net/hashing-security.htm).
 * Copyright (c) 2013, Taylor Hornby
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without 
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice, 
 * this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation 
 * and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE 
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
 * POSSIBILITY OF SUCH DAMAGE.
 */

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>
    public class PasswordHash
    {
        // The following constants may be changed without breaking existing hashes.
        public const int SALT_BYTE_SIZE = 24;
        public const int HASH_BYTE_SIZE = 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_BYTE_SIZE];
            csprng.GetBytes(salt);

            // Hash the password and encode the parameters
            byte[] hash = PBKDF2(password, salt, PBKDF2_ITERATIONS, HASH_BYTE_SIZE);
            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="correctHash">A hash of the correct password.</param>
        /// <returns>True if the password is correct. False otherwise.</returns>
        public static bool ValidatePassword(string password, string correctHash)
        {
            // Extract the parameters from the hash
            char[] delimiter = { ':' };
            string[] split = correctHash.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);
        }
    }
}
canton7
  • 37,633
  • 3
  • 64
  • 77
  • Sorry for the long delay. Basically I've been avoiding implementing the login/registration system for as long as I can (I don't really know why). I've opted for PBKDF2 because my options were bcrypt, scrypt and PBKDF2. Of those only PBKDF2 has a verified implementation in .NET. – matteeyah Sep 05 '15 at 14:59
1

The value of the password is actually never known by the server

Not exactly true. The web server must know the password. It is posted to the web server, and put in memory. This is okay. What's important is that the database server never knows the password. Hashing on the client won't help, as the server controls the JavaScript to hash on the client!

There is no "standard" hashing algorithm. If you want to choose a good one, choose a slow one. The only good choices these days are PBKDF2, scrypt and bcrypt.

Not MD5. Not SHA.

Hash algorithms are worthless (or worth less) if not slow enough. It should take about 250 ms to hash a password on your production servers, so that they are still reasonably slow in an offline attack with GPUs.

They are less valuable if an attacker can brute-force your server undetected.

They are worthless if your users use easy-to-guess passwords (at least for those users).

Community
  • 1
  • 1
Neil McGuigan
  • 46,580
  • 12
  • 123
  • 152
  • They are worthless if not slow enough. They should take about 250 ms to hash a password on your production servers. This really depends how you have your server set up. If your IP will get blocked after a couple of attempts or repeated failed attempts to unlock account locks marks the account as inaccessible for a period (or other measures are taken to prevent brute-forcing) this really stops becoming an issue. – sidjames Jul 07 '15 at 21:05
  • @sidjames IP blocking is useless. Attacker will use anonymizing proxy. Account lockout is problematic too. The real threat is SQL injection and attacker getting user database dump. You need to slow down attacker's offline attempts long enough to detect the hack and warn your users to change their passwords. – Neil McGuigan Jul 07 '15 at 21:34
  • **The web server must know the password.** In this particular application I'll be hashing the password in a client (locally) then sending the hashed password to the RDBMS server. – matteeyah Jul 07 '15 at 22:55
  • @NeilMcGuigan I never use SQL, so never had to worry about SQL injection. Will take your word that this is the main issue, although I thought there were a bunch of libraries now to prevent SQL injection. Anyhow, not making this a debate, will accept your answer as more valid. :) – sidjames Jul 07 '15 at 23:05
  • @sidjames no worries, i wasn't trying to argue, just typing succinctly. sql injection is still #1 threat on owasp – Neil McGuigan Jul 07 '15 at 23:09
  • @matteeyah please take care with 'hashing the password locally' - if it would mean that users would be able to enter with hashed passwords (which is what could be leaked from your database) this is actually insecure. The secure way is: sending the password to the server, hashing it there, compare to what is stored in the database. – MichielB May 12 '21 at 04:51
0

Use the password based key derivation function (PBKDF) to derive a key based on a (securely) randomly genreated salt (PRNG) and password, here's a definitive guide on the subject

https://www.owasp.org/index.php/Password_Storage_Cheat_Sheet

BhavO
  • 2,406
  • 1
  • 11
  • 13
-1

After asking around the same question I came to this solution for password hashing and salt generation. This is copy 'n' paste of the password hashing method I use in various apps.

How you store the resulting hash and salt will depend on your application and deployment.

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

    /// <summary>
    /// Generates a random salt value.
    /// </summary>
    /// <returns></returns>
    public string GenerateSaltValue()
    {
        //Generate a cryptographic random number.
        RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();
        byte[] buff = new byte[64];
        rng.GetBytes(buff);

        // Return a Base64 string representation of the random number.
        return Convert.ToBase64String(buff);
    }

    /// <summary>
    /// Reccomended method to hash user passwords.
    /// <para>This hash is non-reversible and should use the GenerateSaltValue method for creating new salts.</para>
    /// </summary>
    /// <param name="plainText">The supplied password as a byte array</param>
    /// <param name="salt">The salt to use to create the hash.</param>
    /// <returns></returns>
    public byte[] HashPassword(byte[] plainText, byte[] salt)
    {
        HashAlgorithm algorithm = new SHA256Managed();

        byte[] plainTextWithSaltBytes =
          new byte[plainText.Length + salt.Length];

        for (int i = 0; i < plainText.Length; i++)
        {
            plainTextWithSaltBytes[i] = plainText[i];
        }
        for (int i = 0; i < salt.Length; i++)
        {
            plainTextWithSaltBytes[plainText.Length + i] = salt[i];
        }

        return algorithm.ComputeHash(plainTextWithSaltBytes);
    }
sidjames
  • 117
  • 6