วิธีแฮชรหัสผ่าน


117

ฉันต้องการเก็บแฮชของรหัสผ่านไว้ในโทรศัพท์ แต่ไม่แน่ใจว่าต้องทำอย่างไร ดูเหมือนฉันจะพบวิธีการเข้ารหัสเท่านั้น ควรแฮชรหัสผ่านอย่างไรให้ถูกต้อง?

คำตอบ:


62

UPDATE : คำตอบนี้จะล้าสมัยอย่างจริงจัง โปรดใช้คำแนะนำจากhttps://stackoverflow.com/a/10402129/251311แทน

คุณสามารถใช้

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

หรือ

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

เพื่อให้ได้dataเป็นอาร์เรย์ไบต์คุณสามารถใช้ได้

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

และรับสตริงกลับจากmd5dataหรือsha1data

var hashedPassword = ASCIIEncoding.GetString(md5data);

11
ฉันอยากจะแนะนำให้ใช้ SHA1 จริงๆ MD5 เป็น no-no เว้นแต่คุณจะรักษาความเข้ากันได้แบบย้อนหลังกับระบบที่มีอยู่ นอกจากนี้ตรวจสอบให้แน่ใจว่าคุณได้ใส่ไว้ในusingคำสั่งหรือเรียกร้องClear()เมื่อคุณใช้งานเสร็จแล้ว
vcsjones

3
@vcsjones: ฉันไม่ต้องการทำสงครามศักดิ์สิทธิ์ที่นี่ แต่md5ก็ดีพอสำหรับงานเกือบทุกประเภท ช่องโหว่ของมันยังอ้างถึงสถานการณ์ที่เฉพาะเจาะจงมากและเกือบจะต้องให้ผู้โจมตีรู้มากเกี่ยวกับการเข้ารหัส
zerkms

4
ใช้จุด @zerkms แต่ถ้าไม่มีเหตุผลสำหรับความเข้ากันได้แบบย้อนหลังก็ไม่มีเหตุผลที่จะใช้ MD5 "ดีกว่าปลอดภัยกว่าเสียใจ".
vcsjones

4
ไม่มีเหตุผลที่จะใช้ MD5 ในจุดนี้ เนื่องจากเวลาในการคำนวณไม่มีนัยสำคัญจึงไม่มีเหตุผลที่จะใช้ MD5 ยกเว้นความเข้ากันได้กับระบบที่มีอยู่ แม้ว่า MD5 จะ "ดีพอ" แต่ก็ไม่มีค่าใช้จ่ายใด ๆ สำหรับผู้ใช้ SHA ที่ปลอดภัยกว่า ฉันแน่ใจว่า zerkms รู้ว่านี่เป็นความคิดเห็นสำหรับผู้ถามมากกว่า
เจอรัลด์เดวิส

11
ข้อผิดพลาดใหญ่สามประการ: 1) ASCII ลดรหัสผ่านด้วยอักขระที่ผิดปกติอย่างเงียบ ๆ 2) MD5 / SHA-1 / SHA-2 ธรรมดานั้นรวดเร็ว 3) คุณต้องการเกลือ | ใช้ PBKDF2, bcrypt หรือ scrypt แทน PBKDF2 ง่ายที่สุดในคลาส Rfc2898DeriveBytes (ไม่แน่ใจว่ามีอยู่ใน WP7 หรือไม่)
CodesInChaos

300

คำตอบอื่น ๆ ส่วนใหญ่ที่นี่ค่อนข้างล้าสมัยสำหรับแนวทางปฏิบัติที่ดีที่สุดในปัจจุบัน ด้วยเหตุนี้จึงเป็นการประยุกต์ใช้ PBKDF2 / Rfc2898DeriveBytesเพื่อจัดเก็บและตรวจสอบรหัสผ่าน รหัสต่อไปนี้อยู่ในคลาสแบบสแตนด์อะโลนในโพสต์นี้: อีกตัวอย่างหนึ่งของวิธีการจัดเก็บแฮชรหัสผ่านแบบไม่ใช้ พื้นฐานนั้นง่ายมากดังนั้นจึงแบ่งออกเป็น:

ขั้นตอนที่ 1สร้างค่าเกลือด้วย PRNG เข้ารหัส:

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

ขั้นตอนที่ 2สร้าง Rfc2898DeriveBytes และรับค่าแฮช:

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

ขั้นตอนที่ 3รวมไบต์เกลือและรหัสผ่านเพื่อใช้ในภายหลัง:

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

ขั้นตอนที่ 4เปลี่ยนเกลือ + แฮชรวมกันเป็นสตริงสำหรับจัดเก็บ

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

ขั้นตอนที่ 5ตรวจสอบรหัสผ่านที่ผู้ใช้ป้อนกับรหัสผ่านที่จัดเก็บไว้

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

หมายเหตุ: ขึ้นอยู่กับข้อกำหนดด้านประสิทธิภาพของแอปพลิเคชันเฉพาะของคุณค่า100000สามารถลดลงได้ 10000มูลค่าขั้นต่ำควรจะอยู่ที่ประมาณ


8
@Daniel โดยพื้นฐานแล้วโพสต์เกี่ยวกับการใช้สิ่งที่ปลอดภัยมากกว่าแฮชเพียงอย่างเดียว หากคุณเพียงแค่แฮชรหัสผ่านแม้ว่าจะมีเกลืออยู่ก็ตามรหัสผ่านของผู้ใช้ของคุณจะถูกบุกรุก (และมีแนวโน้มที่จะขาย / เผยแพร่) ก่อนที่คุณจะมีโอกาสบอกให้พวกเขาเปลี่ยนด้วยซ้ำ ใช้โค้ดด้านบนเพื่อให้ยากต่อผู้โจมตีไม่ใช่เรื่องง่ายสำหรับนักพัฒนา
csharptest.net

2
@DatVM ไม่เกลือใหม่สำหรับทุกครั้งที่คุณเก็บกัญชา นั่นคือเหตุผลที่รวมกับแฮชสำหรับจัดเก็บข้อมูลเพื่อให้คุณสามารถยืนยันรหัสผ่านได้
csharptest.net

9
@CiprianJijie จุดรวมคือคุณไม่คิดว่าจะทำได้
csharptest.net

9
ในกรณีที่ใครก็ตามกำลังใช้เมธอด VerifyPassword หากคุณต้องการใช้ Linq และเรียกบูลีนให้สั้นลงสิ่งนี้จะทำ: return hash.SequenceEqual (hashBytes.Skip (_saltSize));
Jesú Castillo

2
@ csharptest.net คุณแนะนำขนาดอาร์เรย์แบบใด ขนาดของอาร์เรย์มีผลต่อความปลอดภัยหรือไม่? ฉันไม่รู้เรื่องการแฮช / การเข้ารหัสมากนัก
lennyy

72

จากคำตอบที่ยอดเยี่ยมของ csharptest.netฉันได้เขียนคลาสสำหรับสิ่งนี้:

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

การใช้งาน:

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

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

แฮชตัวอย่างอาจเป็นดังนี้:

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

อย่างที่คุณเห็นฉันได้รวมการทำซ้ำไว้ในแฮชเพื่อการใช้งานที่ง่ายและความเป็นไปได้ในการอัปเกรดสิ่งนี้หากเราต้องการอัปเกรด


หากคุณมีความสนใจในหลัก .net, ฉันยังมีรุ่น .net หลักในรหัสตรวจสอบ


1
เพื่อตรวจสอบว่าหากคุณอัปเกรดเครื่องมือแฮชคุณจะเพิ่มส่วน V1 ของแฮชและคีย์ออกหรือไม่
Mike Cole

1
ใช่นั่นคือแผน จากนั้นคุณจะตัดสินใจตามV1และV2วิธีการยืนยันที่คุณต้องการ
Christian Gollhardt

ขอบคุณสำหรับการตอบกลับและชั้นเรียน ฉันกำลังดำเนินการตามที่เราพูด
Mike Cole

2
ใช่ @NelsonSilva นั่นเป็นเพราะของเกลือ
Christian Gollhardt

1
ด้วยการคัดลอก / วางโค้ดนี้ทั้งหมด (รวมถึงฉันด้วย) ฉันหวังว่าจะมีคนพูดและโพสต์จะได้รับการแก้ไขหากพบปัญหา! :)
อนุ

14

ฉันใช้แฮชและเกลือสำหรับการเข้ารหัสรหัสผ่านของฉัน (เป็นแฮชเดียวกับที่ 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);
}

16
-1 สำหรับการใช้ SHA-1 ธรรมดาซึ่งรวดเร็ว ใช้ฟังก์ชันการหาคีย์แบบช้าเช่น PBKDF2, bcrypt หรือ scrypt
CodesInChaos

2
  1. สร้างเกลือ
  2. สร้างรหัสผ่านแฮชด้วยเกลือ
  3. ประหยัดทั้งกัญชาและเกลือ
  4. ถอดรหัสด้วยรหัสผ่านและเกลือ ... ดังนั้นนักพัฒนาจึงไม่สามารถถอดรหัสรหัสผ่านได้
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); 
      }
 }

1

ฉันคิดว่าการใช้ KeyDerivation.Pbkdf2 นั้นดีกว่า Rfc2898DeriveBytes

ตัวอย่างและคำอธิบาย: รหัสผ่านแฮชใน 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=
 */

นี่คือโค้ดตัวอย่างจากบทความ และเป็นระดับความปลอดภัยขั้นต่ำ เพื่อเพิ่มมันฉันจะใช้แทนพารามิเตอร์ KeyDerivationPrf.HMACSHA1

KeyDerivationPrf.HMACSHA256 หรือ KeyDerivationPrf.HMACSHA512

อย่าประนีประนอมกับการแฮชรหัสผ่าน มีวิธีการทางคณิตศาสตร์หลายวิธีในการเพิ่มประสิทธิภาพการแฮ็กแฮชรหัสผ่าน ผลที่ตามมาอาจเป็นหายนะ เมื่อผู้ร้ายสามารถรับมือกับตารางแฮชรหัสผ่านของผู้ใช้ของคุณได้มันจะค่อนข้างง่ายสำหรับเขาที่จะถอดรหัสรหัสผ่านเนื่องจากอัลกอริทึมอ่อนแอหรือการนำไปใช้งานไม่ถูกต้อง เขามีเวลามาก (เวลา x กำลังคอมพิวเตอร์) ในการถอดรหัสรหัสผ่าน การแฮชรหัสผ่านควรมีความเข้มงวดในการเข้ารหัสเพื่อเปลี่ยน "เวลามาก" เป็น "ระยะเวลาที่ไม่สมเหตุสมผล "

อีกหนึ่งจุดที่จะเพิ่ม

การตรวจสอบแฮชต้องใช้เวลา (และเป็นเรื่องดี) เมื่อผู้ใช้ป้อนชื่อผู้ใช้ผิดจะใช้เวลาไม่นานในการตรวจสอบว่าชื่อผู้ใช้ไม่ถูกต้อง เมื่อชื่อผู้ใช้ถูกต้องเราจะเริ่มการตรวจสอบรหัสผ่านซึ่งเป็นกระบวนการที่ค่อนข้างยาว

สำหรับแฮ็กเกอร์มันจะง่ายมากที่จะเข้าใจว่ามีผู้ใช้อยู่หรือไม่

อย่าตอบกลับทันทีเมื่อชื่อผู้ใช้ผิด

ไม่จำเป็นต้องพูด: อย่าให้คำตอบว่าอะไรผิด "ข้อมูลรับรองไม่ถูกต้อง" โดยทั่วไป


1
BTW คำตอบก่อนหน้าstackoverflow.com/a/57508528/11603057ไม่ถูกต้องและเป็นอันตราย นั่นคือตัวอย่างของการแฮชไม่ใช่การแฮชรหัสผ่าน ต้องมีการทำซ้ำของฟังก์ชันสุ่มหลอกในระหว่างขั้นตอนการหาคีย์ ไม่มี. ฉันไม่สามารถแสดงความคิดเห็นหรือโหวตลงคะแนนได้ (ชื่อเสียงที่ต่ำของฉัน) โปรดอย่าพลาดคำตอบที่ไม่ถูกต้อง!
Albert Lyubarsky

1

@ csharptest.net 's และคริสเตียน Gollhardt ของคำตอบที่ดีขอบคุณมาก แต่หลังจากเรียกใช้รหัสนี้ในการผลิตโดยมีการบันทึกหลายล้านรายการฉันพบว่ามีหน่วยความจำรั่วไหล คลาส RNGCryptoServiceProviderและRfc2898DeriveBytesมาจาก IDisposable แต่เราไม่ได้กำจัดทิ้ง ฉันจะเขียนวิธีแก้ปัญหาของฉันเป็นคำตอบหากมีคนต้องการเวอร์ชันที่จำหน่ายแล้ว

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

    }
}

การใช้งาน:

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

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

0

ใช้คลาสด้านล่างเพื่อสร้างเกลือก่อน ผู้ใช้แต่ละคนจำเป็นต้องมีเกลือที่แตกต่างกันเราสามารถบันทึกไว้ในฐานข้อมูลพร้อมกับคุณสมบัติของผู้ใช้อื่น ๆ ค่ารอบเป็นตัวกำหนดจำนวนครั้งที่จะแฮชรหัสผ่าน

สำหรับรายละเอียดเพิ่มเติม: 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));
        }
    }
}

เราสามารถเรียกใช้จากแอปพลิเคชันคอนโซลได้ดังนี้ ฉันได้ถกรหัสผ่านครั้งที่สองโดยใช้เกลือเดียวกัน

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

    }
}

เอาท์พุต

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.