Cum se face hash-ul unei parole (Programare, C#, Securitate, Hash, Parole, Windows Phone 7)

Skoder a intrebat.

Aș dori să stochez hash-ul unei parole pe telefon, dar nu sunt sigur cum să fac acest lucru. Mi se pare că găsesc doar metode de criptare. Cum ar trebui să fie hașurată corect parola?

9 răspunsuri
zerkms

UPDATE: ACEST RĂSPUNS ESTE SERIOS DEPĂȘIT. Vă rugăm să folosiți în schimb recomandările de pe https://stackoverflow.com/a/10402129/251311 .

Puteți utiliza fie

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

fie

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

pentru a obține data ca matrice de octeți, puteți folosi

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

iar pentru a obține înapoi un șir de caractere din md5data sau sha1data

var hashedPassword = ASCIIEncoding.GetString(md5data);

Comentarii

  • Aș recomanda cu adevărat utilizarea SHA1. MD5 nu este o soluție, cu excepția cazului în care mențineți compatibilitatea retroactivă cu un sistem existent. În plus, asigurați-vă că îl introduceți într-un fișier using sau apelați Clear() pe ea atunci când ați terminat de utilizat implementarea. –  > Por vcsjones.
  • @vcsjones: Nu vreau să fiu un sfânt-război aici, dar… md5 este suficient de bun pentru aproape toate tipurile de sarcini. Vulnerabilitățile sale se referă, de asemenea, la situații foarte specifice și aproape că necesită ca atacatorul să cunoască multe despre criptografie. –  > Por zerkms.
  • @zerkms, am înțeles, dar dacă nu există niciun motiv pentru compatibilitate retroactivă, nu există niciun motiv pentru a folosi MD5. „Mai bine să fii în siguranță decât să-ți pară rău”. –  > Por vcsjones.
  • Nu există niciun motiv pentru a utiliza MD5 în acest moment. Având în vedere că timpul de calcul este nesemnificativ, nu există niciun motiv pentru a utiliza MD5, cu excepția compatibilității cu sistemele existente. Chiar dacă MD5 este „suficient de bun”, nu există niciun cost pentru utilizator în cazul SHA, care este mult mai sigur. Sunt sigur că zerkms știe acest lucru comentariul este mai mult pentru cel care a pus întrebarea. –  > Por Gerald Davis.
  • Trei mari greșeli: 1) ASCII degradează în tăcere parolele cu caractere neobișnuite 2) MD5/SHA-1/SHA-2 simplu este rapid. 3) Aveți nevoie de o sare. | Folosiți în schimb PBKDF2, bcrypt sau scrypt. PBKDF2 este cel mai ușor în clasa Rfc2898DeriveBytes (nu sunt sigur dacă este prezentă pe WP7) –  > Por CodesInChaos.
csharptest.net

Cele mai multe dintre celelalte răspunsuri de aici sunt oarecum depășite de cele mai bune practici de astăzi. Ca atare, iată aplicația de utilizare a PBKDF2/Rfc2898DeriveBytes pentru a stoca și verifica parolele. Următorul cod se află într-o clasă de sine stătătoare în acest post: Un alt exemplu de memorare a unui hash de parolă sărată. Noțiunile de bază sunt foarte ușoare, așa că aici sunt defalcate:

PASUL 1 Creați valoarea sării cu un PRNG criptografic:

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

PASUL 2 Creați Rfc2898DeriveBytes și obțineți valoarea hash:

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

PASUL 3 Combinați octeții de sare și parola pentru utilizare ulterioară:

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

PASUL 4 Transformați combinația sare+hash într-un șir de caractere pentru stocare

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

PASUL 5 Verificarea parolei introduse de utilizator în raport cu o parolă stocată

/* 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();

Notă: În funcție de cerințele de performanță ale aplicației dumneavoastră specifice, valoarea 100000 poate fi redusă. O valoare minimă ar trebui să fie în jur de 10000.

Comentarii

  • @Daniel practic, postul se referă la utilizarea a ceva mai sigur decât un hash singur. Dacă pur și simplu faceți un hash la o parolă, chiar și cu sare, parolele utilizatorilor dvs. vor fi compromise (și probabil vândute/publicate) înainte ca dvs. să aveți ocazia să le spuneți să o schimbe. Folosiți codul de mai sus pentru a îngreuna situația pentru atacator, nu pentru a o face mai ușoară pentru dezvoltator. –  > Por csharptest.net.
  • @DatVM Nu, sare nouă pentru fiecare dată când stocați un hash. de aceea este combinată cu hash-ul pentru stocare, astfel încât să puteți verifica o parolă. –  > Por csharptest.net.
  • @CiprianJijie ideea este că nu ar trebui să poți să o faci. –  > Por csharptest.net.
  • În cazul în care cineva face o metodă VerifyPassword, dacă doriți să folosiți Linq și un apel mai scurt pentru un boolean, acest lucru ar face: return hash.SequenceEqual(hashBytes.Skip(_saltSize)); – –  > Por Jesú Castillo.
  • @csharptest.net Ce fel de dimensiuni de matrice recomandați? oricum dimensiunea matricei afectează mult securitatea? Nu știu prea multe despre hashing/criptografie –  > Por lennyy.
Christian Gollhardt

Pe baza răspunsului excelent al lui csharptest.net, am scris o Clasă pentru acest lucru:

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;
    }
}

Utilizare:

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

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

Un exemplu de hash ar putea fi acesta:

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

După cum puteți vedea, am inclus și iterațiile în hash pentru o utilizare ușoară și pentru a avea posibilitatea de a actualiza acest lucru, în cazul în care avem nevoie de actualizare.


Dacă sunteți interesat de .net core, am și o versiune .net core pe Code Review.

Comentarii

  • Doar pentru a verifica, dacă actualizați motorul de hașurare, ați incrementa secțiunea V1 a hașurii și ați folosi această cheie? –  > Por Mike Cole.
  • Da, acesta este planul. Veți decide apoi pe baza V1 și V2 ce metodă de verificare aveți nevoie. –  > Por Christian Gollhardt.
  • De ce nu folosiți SecureString? –  > Por wa4_tasty_elephant.
  • Da @NelsonSilva. Asta din cauza sare. –  > Por Christian Gollhardt.
  • Cu tot copy/paste-ul acestui cod (inclusiv eu), sper ca cineva să vorbească și postul să fie revizuit dacă se găsește o problemă cu el! 🙂 –  > Por pettys.
Martin

Eu folosesc un hash și o sare pentru criptarea parolei mele (este același hash pe care îl folosește Asp.Net Membership):

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);
}

Comentarii

    16

  • -1 pentru utilizarea SHA-1 simplu, care este rapid. Utilizați o funcție de derivare a cheilor lentă, cum ar fi PBKDF2, bcrypt sau scrypt. –  > Por CodesInChaos.
Pang

În ASP.NET Core, utilizați PasswordHasher<TUser>.
 – Namespace: Microsoft.AspNetCore.Identity
 – Assembly: Microsoft.Extensions.Identity.Core.dll (NuGet | Sursa)


Pentru a hash o parolă, utilizați HashPassword():

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

Pentru a verifica o parolă, utilizați 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();
}


Pro:

  • Face parte din platforma .NET. Mult mai sigură și mai demnă de încredere decât construirea propriului algoritm criptografic.
  • Număr de iterații configurabil și compatibilitate viitoare (a se vedea PasswordHasherOptions).
  • A luat Atac de sincronizare în considerare la verificarea parolei (sursa), la fel ca în cazul în care PHP și Go au făcut.

Contra:

ibrahimozgon

Răspunsurile lui @csharptest.net și ale lui Christian Gollhardt sunt minunate, vă mulțumesc foarte mult. Dar după ce am rulat acest cod în producție cu milioane de înregistrări, am descoperit că există o scurgere de memorie. RNGCryptoServiceProvider și Rfc2898DeriveBytes sunt derivate din IDisposable, dar noi nu le eliminăm. Voi scrie soluția mea ca răspuns dacă cineva are nevoie de o versiune eliminată.

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;
        }

    }
}

Utilizare:

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

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

Bamidele Alegbe
  1. Creați o sare,
  2. Creați o parolă hash cu sare
  3. Salvați atât hash-ul, cât și sarea
  4. decriptați cu parola și sarea… astfel încât dezvoltatorii să nu poată decripta parola
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); 
      }
 }

Albert Lyubarsky

Cred că utilizarea KeyDerivation.Pbkdf2 este mai bună decât Rfc2898DeriveBytes.

Exemplu și explicație:Hash parole în 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=
 */

Acesta este un exemplu de cod din articol. Și este un nivel minim de securitate. pentru a-l crește aș folosi în loc de parametrul KeyDerivationPrf.HMACSHA1 parametrul

KeyDerivationPrf.HMACSHA256 sau KeyDerivationPrf.HMACSHA512.

Nu faceți compromisuri în ceea ce privește hashing-ul parolelor. Există multe metode matematice solide pentru a optimiza hacking-ul hash-urilor de parole. Consecințele ar putea fi dezastruoase. odată ce un infractor rău intenționat poate pune mâna pe tabela hash a parolelor utilizatorilor dumneavoastră, îi va fi relativușor să spargă parolele, având în vedere că algoritmul este slab sau implementarea este incorectă. el are mult timp (timp x puterea calculatorului) pentru a sparge parolele. Hashing-ul parolelor ar trebui să fie puternic din punct de vedere criptografic pentru a transforma „mult timp” în „mult timp”.o cantitate de timp nerezonabilă„.

Încă un punct de adăugat

Verificarea hash-ului necesită timp (și este un lucru bun).Când utilizatorul introduce un nume de utilizator greșit, nu este nevoie de timp pentru a verifica dacă numele de utilizator este incorect.Când numele de utilizator este corect, începem verificarea parolei – este un proces relativ lung.

Pentru un hacker ar fi foarte ușor de înțeles dacă utilizatorul există sau nu.

Asigurați-vă că nu returnează un răspuns imediat atunci când numele de utilizator este greșit.

Inutil de spus : nu dați niciodată un răspuns ce este greșit. Doar un răspuns general de genul „acreditările sunt greșite”.

Comentarii

  • BTW, răspunsul anterior stackoverflow.com/a/57508528/11603057 nu este corect și este dăunător. Acesta este un exemplu de hashing, nu de hashing de parole. Trebuie să fie iterații ale funcției pseudo-aleatoare în timpul procesului de derivare a cheii. Nu există. Nu pot să o comentez sau să o votez în jos (reputația mea scăzută). Vă rog să nu ratați răspunsurile incorecte! –  > Por Albert Lyubarsky.
  • se pare că înțelegeți destul de bine criptografia. Așa că ar fi foarte amabil să oferiți atât criptografia, cât și rutina de verificare – astfel încât oamenii (ca mine) cu mai puține cunoștințe de criptografie să poată folosi propunerea dvs. sigură. M-am uitat pe linkul dvs. la MS și, din păcate, nici ei nu oferă metoda de verificare. –  > Por John Ranger.
  • @John Ranger. 1. Înscrieți-vă utilizatorul. 1.1 Luați numele de utilizator (sau adresa de e-mail), parola și repetați câmpurile de parolă ale acestuia. 1.2 Dacă parola == parola repetată, atunci faceți un hash din parolă. 1.3. Persistați valoarea lui hashed și a numelui de utilizator. 2. 2. Verificare. 2.1. Se iau parola și numele de utilizator ale utilizatorului. 2.2. Se face hashed din acesta. 2.3 Preia hashed-ul stocat cu numele de utilizator al acestuia. 2.4 dacă hashed-ul stocat == hashed-ul curent – OK, dacă nu, NOT OK. Asta este tot. –  > Por Albert Lyubarsky.
  • Încă un lucru, dacă numele de utilizator nu există și nu există nimic de recuperat, faceți o mică întârziere înainte de returnarea NOT OK. Și nu spuneți niciodată utilizatorului ce este în neregulă: numele utilizatorului nu există sau hashed stocat != hashed curent, doar o eroare generală. După N încercări, luați în considerare blocarea utilizatorului timp de K minute pentru a preveni atacul prin forță brutală. –  > Por Albert Lyubarsky.
Anish

Utilizați clasa de mai jos pentru a genera mai întâi o sare. Fiecare utilizator trebuie să aibă o sare diferită, pe care o putem salva în baza de date împreună cu celelalte proprietăți ale utilizatorului. Valoarea runelor decide numărul de ori de câte ori va fi hașurată parola.

Pentru mai multe detalii: 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));
        }
    }
}

O putem apela dintr-o aplicație de consolă după cum urmează. Am modificat parola de două ori folosind valoarea aceeași sare.

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();

    }
}