132

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?

Dharman
  • 21,838
  • 18
  • 57
  • 107
Skoder
  • 3,613
  • 10
  • 41
  • 73

9 Answers9

331

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.

phoenix
  • 3,988
  • 1
  • 29
  • 33
csharptest.net
  • 53,926
  • 10
  • 66
  • 86
  • Thank you for the update and the detailed comments on your code, big help ! – Mickael V. May 06 '13 at 08:00
  • 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? – Daniel Nov 11 '14 at 02:42
  • 9
    @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. – csharptest.net Nov 11 '14 at 03:05
  • 1
    I find a website what go very deep in the argument: http://www.codeproject.com/Articles/704865/Salted-Password-Hashing-Doing-it-Right and here is the code that the author propose: https://crackstation.net/hashing-security.htm#aspsourcecode – Marco Staffoli May 04 '15 at 14:24
  • Please help me clarify: the step 1 should be runned once and use that array forever for an application, is it correct? – Luke Vo May 08 '15 at 08:56
  • 2
    @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. – csharptest.net May 08 '15 at 22:39
  • @csharptest.net thanks, works fine but how I can decript to see initial password ? Thanks – Ciprian Jijie Nov 24 '16 at 12:31
  • 9
    @CiprianJijie the whole point is your not suppose to be able to. – csharptest.net Nov 24 '16 at 15:56
  • 11
    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)); – Jesú Castillo May 04 '17 at 19:17
  • 2
    @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 – lennyy Aug 17 '18 at 08:52
  • @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. – csharptest.net Aug 17 '18 at 14:41
  • 1
    @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. – ibrahimozgon Feb 05 '20 at 07:24
  • For those curious about warnings in Visual Studio while using 'new Rfc2898DeriveBytes()', see this answer: https://stackoverflow.com/questions/18648084/rfc2898-pbkdf2-with-sha256-as-digest-in-c-sharp#answer-51690350 – derekbaker783 Mar 16 '20 at 15:00
  • Is this vulnerable to [Timing Attack](https://en.wikipedia.org/wiki/Timing_attack) when verifying the hash? Should we use [`CryptographicOperations.FixedTimeEquals()`](https://docs.microsoft.com/en-us/dotnet/api/system.security.cryptography.cryptographicoperations.fixedtimeequals) or something, just like what ... – Pang Sep 03 '20 at 08:48
  • ... PHP did in [`password_verify()`](https://github.com/php/php-src/blob/91edb907676805bc4fad3ebc758dd6d797c92a63/ext/standard/password.c#L172-L178) and what Go did in [`ConstantTimeCompare()`](https://github.com/golang/gofrontend/blob/51d2cb40a6475b126ed66cefa2aa6c8dbdc806d0/libgo/go/crypto/subtle/constant_time.go#L19-L21) called by [`CompareHashAndPassword()`](https://github.com/golang/crypto/blob/5c72a883971a4325f8c62bf07b6d38c20ea47a6a/bcrypt/bcrypt.go#L111)? – Pang Sep 03 '20 at 08:48
  • @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. – csharptest.net Sep 03 '20 at 12:30
  • `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 – marsh-wiggle Dec 04 '20 at 19:58
84

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.

Christian Gollhardt
  • 14,865
  • 16
  • 65
  • 99
  • 1
    Just to verify, if you do upgrade the hashing engine you'd increment the V1 section of your hash and key off of that? – Mike Cole May 07 '16 at 18:06
  • 1
    Yes that is the plan. You would then decide based on `V1` and `V2` which verification method you need. – Christian Gollhardt May 07 '16 at 21:42
  • Why don't you use [SecureString](https://msdn.microsoft.com/en-us/library/system.security.securestring(v=vs.110).aspx)? – wa4_tasty_elephant Oct 27 '16 at 14:34
  • 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. – Christian Gollhardt Oct 27 '16 at 16:36
  • Im tryin to use this but everytime I run the hash, using the same string, it creates a new hash. is it normal ? – Nelson Silva May 28 '17 at 22:50
  • 2
    Yes @NelsonSilva. That's because of the [salt](https://en.wikipedia.org/wiki/Salt_(cryptography)). – Christian Gollhardt May 28 '17 at 23:55
  • 3
    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! :) – pettys Mar 20 '19 at 16:03
  • 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](https://codereview.stackexchange.com/questions/176697/net-core-mvc-future-proof-hashing-of-passwords) @pettys – Christian Gollhardt Mar 20 '19 at 16:26
69

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);
Community
  • 1
  • 1
zerkms
  • 230,357
  • 57
  • 408
  • 498
  • 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. – vcsjones Nov 15 '10 at 03:37
  • 3
    @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. – zerkms Nov 15 '10 at 03:40
  • 4
    @zerkms point taken, but if there is no reason for backward compatibility, there is no reason to use MD5. "Better safe than sorry". – vcsjones Nov 15 '10 at 03:42
  • 1
    @zerkms MD5CryptoServiceProvider and SHA1CryptoServiceProvider implement the IDisposable interface explicitly using the `Clear` method. – vcsjones Nov 15 '10 at 03:43
  • 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. – Slartibartfast Nov 15 '10 at 03:43
  • @vcsjones: oh, thanks. I did not even know crypto-classes need to be disposed :-S The samples at http://msdn.microsoft.com/en-us/library/system.security.cryptography.md5cryptoserviceprovider.aspx don't do that in some reason... – zerkms Nov 15 '10 at 03:45
  • @Slartibartfast: indeed, `sha1` is better, but `md5` is not so bad as most of people want to present. – zerkms Nov 15 '10 at 03:47
  • 4
    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. – Gerald Davis Nov 15 '10 at 15:18
  • 1
    Some systems (WPF in my case) do not suppose ASCIIEncoding.GetString(md5data) - instead, you can use var hashedPassword = new System.Text.ASCIIEncoding().GetString(md5data). – Chris Rae Feb 19 '11 at 00:04
  • 2
    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). – milan Nov 30 '11 at 15:46
  • 11
    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 May 11 '12 at 21:21
  • See https://en.wikipedia.org/wiki/MD5#Security for reasons why using MD5 is now a bad idea. – Samuel Parkinson Jun 27 '12 at 16:13
15

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);
}
Brian
  • 5,064
  • 7
  • 33
  • 44
Martin
  • 10,845
  • 8
  • 46
  • 77
12

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:

Pang
  • 8,605
  • 144
  • 77
  • 113
10

@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);
ibrahimozgon
  • 909
  • 7
  • 19
2
  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); 
      }
 }
Bamidele Alegbe
  • 441
  • 3
  • 6
1

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

  • 1
    BTW, the previous answer https://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! – Albert Lyubarsky Oct 05 '19 at 17:31
  • 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. – John Ranger Oct 27 '20 at 22:49
  • @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 Oct 30 '20 at 11:22
  • 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. – Albert Lyubarsky Oct 30 '20 at 11:22
0

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://docs.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

Anish
  • 63
  • 5