How to hash a password

221,835

Solution 1

UPDATE: THIS ANSWER IS SERIOUSLY OUTDATED. Please use the recommendations from the https://stackoverflow.com/a/10402129/251311 instead.

You can either use

var md5 = new MD5CryptoServiceProvider();
var md5data = md5.ComputeHash(data);

or

var sha1 = new SHA1CryptoServiceProvider();
var sha1data = sha1.ComputeHash(data);

To get data as byte array you could use

var data = Encoding.ASCII.GetBytes(password);

and to get back string from md5data or sha1data

var hashedPassword = ASCIIEncoding.GetString(md5data);

Solution 2

Most of the other answers here are somewhat out-of-date with today's best practices. As such here is the application of using PBKDF2/Rfc2898DeriveBytes to store and verify passwords. The following code is in a stand-alone class in this post: Another example of how to store a salted password hash. The basics are really easy, so here it is broken down:

STEP 1 Create the salt value with a cryptographic PRNG:

byte[] salt;
new RNGCryptoServiceProvider().GetBytes(salt = new byte[16]);

STEP 2 Create the Rfc2898DeriveBytes and get the hash value:

var pbkdf2 = new Rfc2898DeriveBytes(password, salt, 100000);
byte[] hash = pbkdf2.GetBytes(20);

STEP 3 Combine the salt and password bytes for later use:

byte[] hashBytes = new byte[36];
Array.Copy(salt, 0, hashBytes, 0, 16);
Array.Copy(hash, 0, hashBytes, 16, 20);

STEP 4 Turn the combined salt+hash into a string for storage

string savedPasswordHash = Convert.ToBase64String(hashBytes);
DBContext.AddUser(new User { ..., Password = savedPasswordHash });

STEP 5 Verify the user-entered password against a stored password

/* Fetch the stored value */
string savedPasswordHash = DBContext.GetUser(u => u.UserName == user).Password;
/* Extract the bytes */
byte[] hashBytes = Convert.FromBase64String(savedPasswordHash);
/* Get the salt */
byte[] salt = new byte[16];
Array.Copy(hashBytes, 0, salt, 0, 16);
/* Compute the hash on the password the user entered */
var pbkdf2 = new Rfc2898DeriveBytes(password, salt, 100000);
byte[] hash = pbkdf2.GetBytes(20);
/* Compare the results */
for (int i=0; i < 20; i++)
    if (hashBytes[i+16] != hash[i])
        throw new UnauthorizedAccessException();

Note: Depending on the performance requirements of your specific application, the value 100000 can be reduced. A minimum value should be around 10000.

Solution 3

Based on csharptest.net's great answer, I have written a Class for this:

public static class SecurePasswordHasher
{
    /// <summary>
    /// Size of salt.
    /// </summary>
    private const int SaltSize = 16;

    /// <summary>
    /// Size of hash.
    /// </summary>
    private const int HashSize = 20;

    /// <summary>
    /// Creates a hash from a password.
    /// </summary>
    /// <param name="password">The password.</param>
    /// <param name="iterations">Number of iterations.</param>
    /// <returns>The hash.</returns>
    public static string Hash(string password, int iterations)
    {
        // Create salt
        byte[] salt;
        new RNGCryptoServiceProvider().GetBytes(salt = new byte[SaltSize]);

        // Create hash
        var pbkdf2 = new Rfc2898DeriveBytes(password, salt, iterations);
        var hash = pbkdf2.GetBytes(HashSize);

        // Combine salt and hash
        var hashBytes = new byte[SaltSize + HashSize];
        Array.Copy(salt, 0, hashBytes, 0, SaltSize);
        Array.Copy(hash, 0, hashBytes, SaltSize, HashSize);

        // Convert to base64
        var base64Hash = Convert.ToBase64String(hashBytes);

        // Format hash with extra information
        return string.Format("$MYHASH$V1${0}${1}", iterations, base64Hash);
    }

    /// <summary>
    /// Creates a hash from a password with 10000 iterations
    /// </summary>
    /// <param name="password">The password.</param>
    /// <returns>The hash.</returns>
    public static string Hash(string password)
    {
        return Hash(password, 10000);
    }

    /// <summary>
    /// Checks if hash is supported.
    /// </summary>
    /// <param name="hashString">The hash.</param>
    /// <returns>Is supported?</returns>
    public static bool IsHashSupported(string hashString)
    {
        return hashString.Contains("$MYHASH$V1$");
    }

    /// <summary>
    /// Verifies a password against a hash.
    /// </summary>
    /// <param name="password">The password.</param>
    /// <param name="hashedPassword">The hash.</param>
    /// <returns>Could be verified?</returns>
    public static bool Verify(string password, string hashedPassword)
    {
        // Check hash
        if (!IsHashSupported(hashedPassword))
        {
            throw new NotSupportedException("The hashtype is not supported");
        }

        // Extract iteration and Base64 string
        var splittedHashString = hashedPassword.Replace("$MYHASH$V1$", "").Split('$');
        var iterations = int.Parse(splittedHashString[0]);
        var base64Hash = splittedHashString[1];

        // Get hash bytes
        var hashBytes = Convert.FromBase64String(base64Hash);

        // Get salt
        var salt = new byte[SaltSize];
        Array.Copy(hashBytes, 0, salt, 0, SaltSize);

        // Create hash with given salt
        var pbkdf2 = new Rfc2898DeriveBytes(password, salt, iterations);
        byte[] hash = pbkdf2.GetBytes(HashSize);

        // Get result
        for (var i = 0; i < HashSize; i++)
        {
            if (hashBytes[i + SaltSize] != hash[i])
            {
                return false;
            }
        }
        return true;
    }
}

Usage:

// Hash
var hash = SecurePasswordHasher.Hash("mypassword");

// Verify
var result = SecurePasswordHasher.Verify("mypassword", hash);

A sample hash could be this:

$MYHASH$V1$10000$Qhxzi6GNu/Lpy3iUqkeqR/J1hh8y/h5KPDjrv89KzfCVrubn

As you can see, I also have included the iterations in the hash for easy usage and the possibility to upgrade this, if we need to upgrade.


If you are interested in .net core, I also have a .net core version on Code Review.

Solution 4

@csharptest.net's and Christian Gollhardt's answers are great, thank you very much. But after running this code on production with millions of record, I discovered there is a memory leak. RNGCryptoServiceProvider and Rfc2898DeriveBytes classes are derived from IDisposable but we don't dispose of them. I will write my solution as an answer if someone needs with disposed version.

public static class SecurePasswordHasher
{
    /// <summary>
    /// Size of salt.
    /// </summary>
    private const int SaltSize = 16;

    /// <summary>
    /// Size of hash.
    /// </summary>
    private const int HashSize = 20;

    /// <summary>
    /// Creates a hash from a password.
    /// </summary>
    /// <param name="password">The password.</param>
    /// <param name="iterations">Number of iterations.</param>
    /// <returns>The hash.</returns>
    public static string Hash(string password, int iterations)
    {
        // Create salt
        using (var rng = new RNGCryptoServiceProvider())
        {
            byte[] salt;
            rng.GetBytes(salt = new byte[SaltSize]);
            using (var pbkdf2 = new Rfc2898DeriveBytes(password, salt, iterations))
            {
                var hash = pbkdf2.GetBytes(HashSize);
                // Combine salt and hash
                var hashBytes = new byte[SaltSize + HashSize];
                Array.Copy(salt, 0, hashBytes, 0, SaltSize);
                Array.Copy(hash, 0, hashBytes, SaltSize, HashSize);
                // Convert to base64
                var base64Hash = Convert.ToBase64String(hashBytes);

                // Format hash with extra information
                return $"$HASH|V1${iterations}${base64Hash}";
            }
        }

    }

    /// <summary>
    /// Creates a hash from a password with 10000 iterations
    /// </summary>
    /// <param name="password">The password.</param>
    /// <returns>The hash.</returns>
    public static string Hash(string password)
    {
        return Hash(password, 10000);
    }

    /// <summary>
    /// Checks if hash is supported.
    /// </summary>
    /// <param name="hashString">The hash.</param>
    /// <returns>Is supported?</returns>
    public static bool IsHashSupported(string hashString)
    {
        return hashString.Contains("HASH|V1$");
    }

    /// <summary>
    /// Verifies a password against a hash.
    /// </summary>
    /// <param name="password">The password.</param>
    /// <param name="hashedPassword">The hash.</param>
    /// <returns>Could be verified?</returns>
    public static bool Verify(string password, string hashedPassword)
    {
        // Check hash
        if (!IsHashSupported(hashedPassword))
        {
            throw new NotSupportedException("The hashtype is not supported");
        }

        // Extract iteration and Base64 string
        var splittedHashString = hashedPassword.Replace("$HASH|V1$", "").Split('$');
        var iterations = int.Parse(splittedHashString[0]);
        var base64Hash = splittedHashString[1];

        // Get hash bytes
        var hashBytes = Convert.FromBase64String(base64Hash);

        // Get salt
        var salt = new byte[SaltSize];
        Array.Copy(hashBytes, 0, salt, 0, SaltSize);

        // Create hash with given salt
        using (var pbkdf2 = new Rfc2898DeriveBytes(password, salt, iterations))
        {
            byte[] hash = pbkdf2.GetBytes(HashSize);

            // Get result
            for (var i = 0; i < HashSize; i++)
            {
                if (hashBytes[i + SaltSize] != hash[i])
                {
                    return false;
                }
            }

            return true;
        }

    }
}

Usage:

// Hash
var hash = SecurePasswordHasher.Hash("mypassword");

// Verify
var result = SecurePasswordHasher.Verify("mypassword", hash);

Solution 5

In ASP.NET Core, use PasswordHasher<TUser>.
 • Namespace: Microsoft.AspNetCore.Identity
 • Assembly: Microsoft.Extensions.Identity.Core.dll (NuGet | Source)


To hash a password, use HashPassword():

var hashedPassword = new PasswordHasher<object?>().HashPassword(null, password);

To verify a password, use VerifyHashedPassword():

var passwordVerificationResult = new PasswordHasher<object?>().VerifyHashedPassword(null, hashedPassword, password);
switch (passwordVerificationResult)
{
    case PasswordVerificationResult.Failed:
        Console.WriteLine("Password incorrect.");
        break;
    
    case PasswordVerificationResult.Success:
        Console.WriteLine("Password ok.");
        break;
    
    case PasswordVerificationResult.SuccessRehashNeeded:
        Console.WriteLine("Password ok but should be rehashed and updated.");
        break;
    
    default:
        throw new ArgumentOutOfRangeException();
}


Pros:

  • Part of the .NET platform. Much safer and trustworthier than building your own crypto algorithm.
  • Configurable iteration count and future compatibility (see PasswordHasherOptions).
  • Took Timing Attack into consideration when verifying password (source), just like what PHP and Go did.

Cons:

Share:
221,835

Related videos on Youtube

Skoder
Author by

Skoder

Updated on December 08, 2021

Comments

  • Skoder
    Skoder over 2 years

    I'd like to store the hash of a password on the phone, but I'm not sure how to do it. I can only seem to find encryption methods. How should the password be hashed properly?

  • vcsjones
    vcsjones over 13 years
    I would REALLY recommend using SHA1. MD5 is a no-no unless you are maintaining backward compatibility with an existing system. In addition, make sure you put it in a using statement or call Clear() on it when you are done using the implementation.
  • zerkms
    zerkms over 13 years
    @vcsjones: I don't want to holy-war here, but md5 is good enough for the almost all kind of tasks. Its vulnerabilities also refers to very specific situations and almost requires for attacker to know a lot about cryptography.
  • vcsjones
    vcsjones over 13 years
    @zerkms point taken, but if there is no reason for backward compatibility, there is no reason to use MD5. "Better safe than sorry".
  • vcsjones
    vcsjones over 13 years
    @zerkms MD5CryptoServiceProvider and SHA1CryptoServiceProvider implement the IDisposable interface explicitly using the Clear method.
  • Slartibartfast
    Slartibartfast over 13 years
    While I agree wholeheartedly with zerkms, if you have the option between the two, and the difficulty is approximately the same, choose SHA1 or allow for either.
  • zerkms
    zerkms over 13 years
    @vcsjones: oh, thanks. I did not even know crypto-classes need to be disposed :-S The samples at msdn.microsoft.com/en-us/library/… don't do that in some reason...
  • zerkms
    zerkms over 13 years
    @Slartibartfast: indeed, sha1 is better, but md5 is not so bad as most of people want to present.
  • Gerald Davis
    Gerald Davis over 13 years
    No reason to use MD5 at this point. Given that computation time is insignificant there is no reason to use MD5 except as compatibility with existing systems. Even if MD5 is "good enough" there is no cost with user the far more secure SHA. I am sure zerkms know this the comment is more for the questioner.
  • Chris Rae
    Chris Rae about 13 years
    Some systems (WPF in my case) do not suppose ASCIIEncoding.GetString(md5data) - instead, you can use var hashedPassword = new System.Text.ASCIIEncoding().GetString(md5data).
  • milan
    milan over 12 years
    Surprisingly no one says why it's bad to use MD5: because it's incredibly fast (say takes microseconds to hash a 5-10 char string), so it's possible to run a brute force attack to find the password for a given md5 hash (say 10 billion possible password 5 chars long => 10000 seconds or less than 3 hours on a single cpu).
  • CodesInChaos
    CodesInChaos almost 12 years
    Three big mistakes: 1) ASCII silently degrades passwords with unusual characters 2) Plain MD5/SHA-1/SHA-2 is fast. 3) You need a salt. | Use PBKDF2, bcrypt or scrypt instead. PBKDF2 is easiest in the Rfc2898DeriveBytes class (not sure if present on WP7)
  • CodesInChaos
    CodesInChaos almost 12 years
    -1 for using plain SHA-1, which is fast. Use a slow key derivation function, such as PBKDF2, bcrypt or scrypt.
  • Samuel Parkinson
    Samuel Parkinson almost 12 years
    See en.wikipedia.org/wiki/MD5#Security for reasons why using MD5 is now a bad idea.
  • Mickael V.
    Mickael V. almost 11 years
    Thank you for the update and the detailed comments on your code, big help !
  • Daniel
    Daniel over 9 years
    This is more like a question, isn't easier, for checking the password, to hash the given password and compare the stored string with the one given by the user and hashed again?
  • csharptest.net
    csharptest.net over 9 years
    @Daniel basically the post is about using something more secure than a hash alone. If you simply hash a password, even with salt, your users passwords are going to be compromised (and likely sold/published) before you even have a chance to tell them to change it. Use the above code to make it difficult for the attacker, not easy for the developer.
  • Marco Staffoli
    Marco Staffoli almost 9 years
    I find a website what go very deep in the argument: codeproject.com/Articles/704865/… and here is the code that the author propose: crackstation.net/hashing-security.htm#aspsourcecode
  • Luke Vo
    Luke Vo almost 9 years
    Please help me clarify: the step 1 should be runned once and use that array forever for an application, is it correct?
  • csharptest.net
    csharptest.net almost 9 years
    @DatVM No, new salt for every time you store a hash. that is why it's combined with the hash for storage so that you can verify a password.
  • Mike Cole
    Mike Cole almost 8 years
    Just to verify, if you do upgrade the hashing engine you'd increment the V1 section of your hash and key off of that?
  • Christian Gollhardt
    Christian Gollhardt almost 8 years
    Yes that is the plan. You would then decide based on V1 and V2 which verification method you need.
  • wa4_tasty_elephant
    wa4_tasty_elephant over 7 years
    Why don't you use SecureString?
  • Christian Gollhardt
    Christian Gollhardt over 7 years
    In my scenario there is no need for it @Wavum. The password is provided by the user via web, so this is a bigger atack vector then the possibility that somebodody has access to my server and reads the ram. Anyway if you work with client application, the usage of SecureString makes sence.
  • Ciprian Jijie
    Ciprian Jijie over 7 years
    @csharptest.net thanks, works fine but how I can decript to see initial password ? Thanks
  • csharptest.net
    csharptest.net over 7 years
    @CiprianJijie the whole point is your not suppose to be able to.
  • Jesú Castillo
    Jesú Castillo almost 7 years
    In case anyone is doing a VerifyPassword method, if you would like to use Linq and a shorter call for a boolean, this would do: return hash.SequenceEqual(hashBytes.Skip(_saltSize));
  • Nelson Silva
    Nelson Silva almost 7 years
    Im tryin to use this but everytime I run the hash, using the same string, it creates a new hash. is it normal ?
  • Christian Gollhardt
    Christian Gollhardt almost 7 years
    Yes @NelsonSilva. That's because of the salt.
  • lenny
    lenny over 5 years
    @csharptest.net What kind of array sizes do you recommend? does the size of the array affect the security much anyway? I don't know that much about hashing/cryptography
  • csharptest.net
    csharptest.net over 5 years
    @lenny The primary means of adjusting the “security” of this routine is by adjusting the rounds of PBKDF2 iterations. The size pf the salt can be increased; however, since it is stored with the hash increasing it really adds very minimal complexity.
  • pettys
    pettys about 5 years
    With all the copy/pasting of this code (including me), I hope someone speaks up and the post gets revised if an issue is found with it! :)
  • Christian Gollhardt
    Christian Gollhardt about 5 years
    In the meantime, you probably want add some more iterations here return Hash(password, 10000);, other than that I have had no issues so far and it's in production since ~ 4 years :) For the newer .net core, I also written a class, which lives at code review @pettys
  • Albert Lyubarsky
    Albert Lyubarsky over 4 years
    BTW, the previous answer stackoverflow.com/a/57508528/11603057 is not correct and harmful. That is an example of hashing, not password hashing. Must be iterations of the pseudo-random function during the key derivation process. There is no. I cannot comment it or downvote (my low reputation). Please don't miss out uncorrect answers!
  • ibrahimozgon
    ibrahimozgon about 4 years
    @csharptest.net your answer is great, thank you very much. But after running this code on production with millions of record, I discovered there is a memory leak. RNGCryptoServiceProvider and Rfc2898DeriveBytes classes are derived from IDisposable but you don't dispose them. I will write my solution as an answer if someone needs with disposed version.
  • derekbaker783
    derekbaker783 about 4 years
    For those curious about warnings in Visual Studio while using 'new Rfc2898DeriveBytes()', see this answer: stackoverflow.com/questions/18648084/…
  • Pang
    Pang over 3 years
    Is this vulnerable to Timing Attack when verifying the hash? Should we use CryptographicOperations.FixedTimeEquals() or something, just like what ...
  • Pang
    Pang over 3 years
    ... PHP did in password_verify() and what Go did in ConstantTimeCompare() called by CompareHashAndPassword()?
  • csharptest.net
    csharptest.net over 3 years
    @pang for timing you need to also consider user not found. The easiest implementation is that if anything fails in a logon attempt you wait a random amount of time and then throw unauthorized. With a long enough minimum delay you also slow down a single connection’s number of attempts.
  • John Ranger
    John Ranger over 3 years
    you seem to understand crypto quite well. So it would be really kind to provide both the crypto and the verification routine - so that people (like me) with less crypto skills just can use your secure proposition. I have looked up your link over to MS - and unfortunately they also don't provide the verification method.
  • Albert Lyubarsky
    Albert Lyubarsky over 3 years
    @John Ranger. 1. Enroll your user. 1.1 Take his user name (or email), password and repeat password fields. 1.2 If password == repeat password then make hashed from password. 1.3. Persist the value of hashed and user name. 2. Verification. 2.1 Take user's password and user name. 2.2 make hashed from it. 2.3 Retrieve stored hashed by his user name. 2.4 if stored hashed == current hashed - OK if not NOT OK. That's all.
  • Albert Lyubarsky
    Albert Lyubarsky over 3 years
    One more thing if user name doesn't exist and nothing to retrieve make slight delay before return NOT OK. And never tell the user what's wrong : user name doesn't exist or stored hashed != current hashed, just general error. After N retries consider to lock the user for K minutes to prevent Brutal Force Attack.
  • marsh-wiggle
    marsh-wiggle over 3 years
    Most of the other answers here are somewhat out-of-date with today's best practices. this is from 2012 and "today" is 2020. When anyone reads this it's probably 2030
  • Klyuch
    Klyuch over 2 years
    One more to Cons: <TUser> as fake parameter. And it is part of ASP.NET Core Identity, that means in future it could have some dependencies of Identity framework.
  • mlaan
    mlaan about 2 years
    It looks to me like a '$' got lost in your version of IsHashSupported.