How to hash a password
Asked Answered
R

12

166

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?

Rincon answered 15/11, 2010 at 3:32 Comment(0)
B
75

UPDATE: THIS ANSWER IS SERIOUSLY OUTDATED. Please use the recommendations from https://stackoverflow.com/a/10402129 or https://stackoverflow.com/a/73125177 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);
Bombard answered 15/11, 2010 at 3:35 Comment(12)
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.Desensitize
@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.Bombard
@Bombard point taken, but if there is no reason for backward compatibility, there is no reason to use MD5. "Better safe than sorry".Desensitize
@Bombard MD5CryptoServiceProvider and SHA1CryptoServiceProvider implement the IDisposable interface explicitly using the Clear method.Desensitize
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.Glosseme
@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...Bombard
@Slartibartfast: indeed, sha1 is better, but md5 is not so bad as most of people want to present.Bombard
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.Samphire
Some systems (WPF in my case) do not suppose ASCIIEncoding.GetString(md5data) - instead, you can use var hashedPassword = new System.Text.ASCIIEncoding().GetString(md5data).Goodrich
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).Dropsonde
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)Licensee
See en.wikipedia.org/wiki/MD5#Security for reasons why using MD5 is now a bad idea.Alow
J
379

Most of the other answers here are somewhat outdated considering today's (year 2012) best practices.

The most robust password-hashing algorithm that's natively available in .NET is PBKDF2, represented by the Rfc2898DeriveBytes class. 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.

Joyann answered 1/5, 2012 at 18:12 Comment(19)
Thank you for the update and the detailed comments on your code, big help !Jobyna
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?Judon
@Judon 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.Joyann
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#aspsourcecodeJohnsson
Please help me clarify: the step 1 should be runned once and use that array forever for an application, is it correct?Anticatalyst
@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.Joyann
@Joyann thanks, works fine but how I can decript to see initial password ? ThanksAcoustician
@CiprianJijie the whole point is your not suppose to be able to.Joyann
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));Seicento
@Joyann 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/cryptographyFibrilliform
@Fibrilliform 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.Joyann
@Joyann 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.Brigade
For those curious about warnings in Visual Studio while using 'new Rfc2898DeriveBytes()', see this answer: #18648584Boote
Is this vulnerable to Timing Attack when verifying the hash? Should we use CryptographicOperations.FixedTimeEquals() or something, just like what ...Solubilize
... PHP did in password_verify() and what Go did in ConstantTimeCompare() called by CompareHashAndPassword()?Solubilize
@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.Joyann
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 2030Ligurian
thnx may i ask which one is better normal hashing or encrypt ... is there differentSurefire
If you want to use this in 2022 (.NET 6 or above), then take a look at this answer.Despoil
C
103

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.

Coronel answered 24/8, 2015 at 20:52 Comment(9)
Just to verify, if you do upgrade the hashing engine you'd increment the V1 section of your hash and key off of that?Tamayo
Yes that is the plan. You would then decide based on V1 and V2 which verification method you need.Coronel
Why don't you use SecureString?Clipped
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.Coronel
Im tryin to use this but everytime I run the hash, using the same string, it creates a new hash. is it normal ?Precognition
Yes @NelsonSilva. That's because of the salt.Coronel
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! :)Mushro
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 @MushroCoronel
See https://mcmap.net/q/129019/-how-to-hash-a-password for potential memory leakDelila
B
75

UPDATE: THIS ANSWER IS SERIOUSLY OUTDATED. Please use the recommendations from https://stackoverflow.com/a/10402129 or https://stackoverflow.com/a/73125177 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);
Bombard answered 15/11, 2010 at 3:35 Comment(12)
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.Desensitize
@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.Bombard
@Bombard point taken, but if there is no reason for backward compatibility, there is no reason to use MD5. "Better safe than sorry".Desensitize
@Bombard MD5CryptoServiceProvider and SHA1CryptoServiceProvider implement the IDisposable interface explicitly using the Clear method.Desensitize
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.Glosseme
@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...Bombard
@Slartibartfast: indeed, sha1 is better, but md5 is not so bad as most of people want to present.Bombard
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.Samphire
Some systems (WPF in my case) do not suppose ASCIIEncoding.GetString(md5data) - instead, you can use var hashedPassword = new System.Text.ASCIIEncoding().GetString(md5data).Goodrich
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).Dropsonde
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)Licensee
See en.wikipedia.org/wiki/MD5#Security for reasons why using MD5 is now a bad idea.Alow
D
36

2022 (.NET 6+) solution:

Most of the other answers here were written years ago and are therefore not taking advantage of many of the more recent features introduced in newer versions of .NET. The same thing can now be achieved much more simply and with a lot less boilerplate and noise. The solution I'm proposing also provides extra robustness by allowing you to modify the settings (e.g. iterations count, etc.) in the future without actually breaking old hashes (which is what would happen if you used the accepted answer's proposed solution, for example).

Pros:

  • Uses the new static Rfc2898DeriveBytes.Pbkdf2() method introduced in .NET 6, eliminating the need to instantiate and also dispose the object every single time.

  • Uses the new RandomNumberGenerator class and its static GetBytes method — introduced in .NET 6 — to generate the salt. The RNGCryptoServiceProvider class used in the accepted answer is obsolete.

  • Uses the CryptographicOperations.FixedTimeEquals method (introduced in .NET Core 2.1) for comparing the key bytes in the Verify method, instead of doing the comparison by hand — like the accepted answer is doing. This, in addition to removing a lot of noisy boilerplate, also nullifies timing attacks.

  • Uses SHA-256 instead of the default SHA-1 as the underlying algorithm, just to be on the safe side, as the latter is a more robust and reliable algorithm.

  • The returned string from the Hash method has the following structure:

    [key]:[salt]:[iterations]:[algorithm]

    This is the most important advantage of this solution, it means we're basically including metadata about the configurations used to create the hash in the final string. This allows us to change the settings (such as the number of iterations, salt/key size, etc.) in our hasher class in the future without breaking previous hashes created with the old settings. This is something that the accepted answer, for example, doesn't take into account, as it's relying on the "current" configuration values to verify hashes.

Other points:

  • I'm using the hexadecimal representation of the key and the salt in the returned hash string. You can instead use base64 if you prefer, simply by changing every occurrence of Convert.ToHexString and Convert.FromHexString to Convert.ToBase64 and Convert.FromBase64 respectively. The rest of the logic remains exactly the same.
  • The often recommended salt size is 64 bits or above. I've set it to 128 bits.
  • The key size should normally be the same as the natural output size of your chosen algorithm — see this comment. In our case, as I mentioned earlier, the underlying algorithm is SHA-256, whose output size is 256 bits, which is precisely what we're setting our key size to.
  • If you plan to use this for storing user passwords, it's usually recommended to use at least 10,000 iterations or more. I've set the default value to 50,000, which you can of course change as you see fit.

The code:

public static class SecretHasher
{
    private const int _saltSize = 16; // 128 bits
    private const int _keySize = 32; // 256 bits
    private const int _iterations = 50000;
    private static readonly HashAlgorithmName _algorithm = HashAlgorithmName.SHA256;

    private const char segmentDelimiter = ':';

    public static string Hash(string input)
    {
        byte[] salt = RandomNumberGenerator.GetBytes(_saltSize);
        byte[] hash = Rfc2898DeriveBytes.Pbkdf2(
            input,
            salt,
            _iterations,
            _algorithm,
            _keySize
        );
        return string.Join(
            segmentDelimiter,
            Convert.ToHexString(hash),
            Convert.ToHexString(salt),
            _iterations,
            _algorithm
        );
    }

    public static bool Verify(string input, string hashString)
    {
        string[] segments = hashString.Split(segmentDelimiter);
        byte[] hash = Convert.FromHexString(segments[0]);
        byte[] salt = Convert.FromHexString(segments[1]);
        int iterations = int.Parse(segments[2]);
        HashAlgorithmName algorithm = new HashAlgorithmName(segments[3]);
        byte[] inputHash = Rfc2898DeriveBytes.Pbkdf2(
            input,
            salt,
            iterations,
            algorithm,
            hash.Length
        );
        return CryptographicOperations.FixedTimeEquals(inputHash, hash);
    }
}

Usage:

// Hash:
string password = "...";
string hashed = SecretHasher.Hash(password);

// Verify:
string enteredPassword = "...";
bool isPasswordCorrect = SecretHasher.Verify(enteredPassword, hashed);
Despoil answered 26/7, 2022 at 14:26 Comment(3)
What is _pdkdf2Algorithm supposed to be? It's undefined.Centi
@Centi It should actually be _algorithm, I fixed it. Thanks for catching that.Despoil
This should be the accepted answer in 2022. Beautiful.Caret
B
18

@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);
Brigade answered 5/2, 2020 at 7:43 Comment(1)
It looks to me like a '$' got lost in your version of IsHashSupported.Gaullist
S
17

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:

Solubilize answered 4/9, 2020 at 0:35 Comment(1)
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.Flexion
T
16

I use a hash and a salt for my password encryption (it's the same hash that Asp.Net Membership uses):

private string PasswordSalt
{
   get
   {
      var rng = new RNGCryptoServiceProvider();
      var buff = new byte[32];
      rng.GetBytes(buff);
      return Convert.ToBase64String(buff);
   }
}

private string EncodePassword(string password, string salt)
{
   byte[] bytes = Encoding.Unicode.GetBytes(password);
   byte[] src = Encoding.Unicode.GetBytes(salt);
   byte[] dst = new byte[src.Length + bytes.Length];
   Buffer.BlockCopy(src, 0, dst, 0, src.Length);
   Buffer.BlockCopy(bytes, 0, dst, src.Length, bytes.Length);
   HashAlgorithm algorithm = HashAlgorithm.Create("SHA1");
   byte[] inarray = algorithm.ComputeHash(dst);
   return Convert.ToBase64String(inarray);
}
Thicket answered 15/11, 2010 at 3:49 Comment(1)
-1 for using plain SHA-1, which is fast. Use a slow key derivation function, such as PBKDF2, bcrypt or scrypt.Licensee
L
4
  1. Create a salt,
  2. Create a hash password with salt
  3. Save both hash and salt
  4. decrypt with password and salt... so developers cant decrypt password
public class CryptographyProcessor
{
    public string CreateSalt(int size)
    {
        //Generate a cryptographic random number.
          RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();
         byte[] buff = new byte[size];
         rng.GetBytes(buff);
         return Convert.ToBase64String(buff);
    }


      public string GenerateHash(string input, string salt)
      { 
         byte[] bytes = Encoding.UTF8.GetBytes(input + salt);
         SHA256Managed sHA256ManagedString = new SHA256Managed();
         byte[] hash = sHA256ManagedString.ComputeHash(bytes);
         return Convert.ToBase64String(hash);
      }

      public bool AreEqual(string plainTextInput, string hashedInput, string salt)
      {
           string newHashedPin = GenerateHash(plainTextInput, salt);
           return newHashedPin.Equals(hashedInput); 
      }
 }
Lengthen answered 15/8, 2019 at 10:50 Comment(0)
G
3

I think using KeyDerivation.Pbkdf2 is better than Rfc2898DeriveBytes.

Example and explanation: Hash passwords in ASP.NET Core

using System;
using System.Security.Cryptography;
using Microsoft.AspNetCore.Cryptography.KeyDerivation;
 
public class Program
{
    public static void Main(string[] args)
    {
        Console.Write("Enter a password: ");
        string password = Console.ReadLine();
 
        // generate a 128-bit salt using a secure PRNG
        byte[] salt = new byte[128 / 8];
        using (var rng = RandomNumberGenerator.Create())
        {
            rng.GetBytes(salt);
        }
        Console.WriteLine($"Salt: {Convert.ToBase64String(salt)}");
 
        // derive a 256-bit subkey (use HMACSHA1 with 10,000 iterations)
        string hashed = Convert.ToBase64String(KeyDerivation.Pbkdf2(
            password: password,
            salt: salt,
            prf: KeyDerivationPrf.HMACSHA1,
            iterationCount: 10000,
            numBytesRequested: 256 / 8));
        Console.WriteLine($"Hashed: {hashed}");
    }
}
 
/*
 * SAMPLE OUTPUT
 *
 * Enter a password: Xtw9NMgx
 * Salt: NZsP6NnmfBuYeJrrAKNuVQ==
 * Hashed: /OOoOer10+tGwTRDTrQSoeCxVTFr6dtYly7d0cPxIak=
 */

This is a sample code from the article. And it's a minimum security level. To increase it I would use instead of KeyDerivationPrf.HMACSHA1 parameter

KeyDerivationPrf.HMACSHA256 or KeyDerivationPrf.HMACSHA512.

Don't compromise on password hashing. There are many mathematically sound methods to optimize password hash hacking. Consequences could be disastrous. Once a malefactor can get his hands on password hash table of your users it would be relatively easy for him to crack passwords given algorithm is weak or implementation is incorrect. He has a lot of time (time x computer power) to crack passwords. Password hashing should be cryptographically strong to turn "a lot of time" to "unreasonable amount of time".

One more point to add

Hash verification takes time (and it's good). When user enters wrong user name it's takes no time to check that user name is incorrect. When user name is correct we start password verification - it's relatively long process.

For a hacker it would be very easy to understand if user exists or doesn't.

Make sure not to return immediate answer when user name is wrong.

Needless to say : never give an answer what is wrong. Just general "Credentials are wrong".

Girasol answered 5/10, 2019 at 11:24 Comment(4)
BTW, the previous answer https://mcmap.net/q/129019/-how-to-hash-a-password 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!Girasol
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.Agra
@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.Girasol
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.Girasol
I
1

Use the below class to Generate a Salt first. Each user needs to have a different salt, we can save it in the database along with the other user properties. The rounds value decides the number of times the password will be hashed.

For more details: https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.rfc2898derivebytes.-ctor?view=netcore-3.1#System_Security_Cryptography_Rfc2898DeriveBytes__ctor_System_Byte___System_Byte___System_Int32_

public class HashSaltWithRounds
{
    int saltLength = 32;
    public byte[] GenerateSalt()
    {
        using (var randomNumberGenerator = new RNGCryptoServiceProvider())
        {
            var randomNumber = new byte[saltLength];
            randomNumberGenerator.GetBytes(randomNumber);
            return randomNumber;
        }
    }

    public string HashDataWithRounds(byte[] password, byte[] salt, int rounds)
    {
        using(var rfc2898= new Rfc2898DeriveBytes(password, salt, rounds))
        {
            return Convert.ToBase64String(rfc2898.GetBytes(32));
        }
    }
}

We can call it from a console application as follows. I have hashed the password twice using the same salt.

public class Program
{
    public static void Main(string[] args)
    {
        int numberOfIterations = 99;
        var hashFunction = new HashSaltWithRounds();

        string password = "Your Password Here";
        byte[] salt = hashFunction.GenerateSalt();

        var hashedPassword1 = hashFunction.HashDataWithRounds(Encoding.UTF8.GetBytes(password), salt, numberOfIterations);
        var hashedPassword2 = hashFunction.HashDataWithRounds(Encoding.UTF8.GetBytes(password), salt, numberOfIterations);

        Console.WriteLine($"hashedPassword1 :{hashedPassword1}");
        Console.WriteLine($"hashedPassword2 :{hashedPassword2}");
        Console.WriteLine(hashedPassword1.Equals(hashedPassword2));

        Console.ReadLine();

    }
}

Output

Illfavored answered 13/8, 2020 at 9:18 Comment(0)
C
0
  var sha1 = SHA1.Create();      

  byte[] bytes = Encoding.UTF8.GetBytes("Your String Data");

  byte[] hash = sha1.ComputeHash(bytes);


    var sBuilder = new StringBuilder();

    for (int i = 0; i < hash.Length; i++)
    {
        sBuilder.Append(hash[i].ToString("X"));
    }

    Console.WriteLine(sBuilder.ToString());  //Hash value in Hex
Collative answered 8/12, 2022 at 12:43 Comment(0)
R
0

Here's an example using .NET 7.0

using Microsoft.AspNetCore.Cryptography.KeyDerivation;
using System.Security.Cryptography;

Console.Write("Enter a password: ");
string? password = Console.ReadLine();

// Generate a 128-bit salt using a sequence of
// cryptographically strong random bytes.
byte[] salt = RandomNumberGenerator.GetBytes(128 / 8); // divide by 8 to convert bits to bytes
Console.WriteLine($"Salt: {Convert.ToBase64String(salt)}");

// derive a 256-bit subkey (use HMACSHA256 with 100,000 iterations)
string hashed = Convert.ToBase64String(KeyDerivation.Pbkdf2(
    password: password!,
    salt: salt,
    prf: KeyDerivationPrf.HMACSHA256,
    iterationCount: 100000,
    numBytesRequested: 256 / 8));

Console.WriteLine($"Hashed: {hashed}");

/*
 * SAMPLE OUTPUT
 *
 * Enter a password: Xtw9NMgx
 * Salt: CGYzqeN4plZekNC88Umm1Q==
 * Hashed: Gt9Yc4AiIvmsC1QQbe2RZsCIqvoYlst2xbz0Fs8aHnw=
 */
Respective answered 28/10, 2023 at 19:34 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.