รหัสผ่านแฮและเกลือใน C #


178

ฉันเพิ่งจะผ่านหนึ่งในบทความของ DavidHayden เมื่อ Hashing รหัสผ่านของผู้ใช้

จริงๆฉันไม่สามารถรับสิ่งที่เขาพยายามบรรลุ

นี่คือรหัสของเขา:

private static string CreateSalt(int size)
{
    //Generate a cryptographic random number.
    RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider();
    byte[] buff = new byte[size];
    rng.GetBytes(buff);

    // Return a Base64 string representation of the random number.
    return Convert.ToBase64String(buff);
}

private static string CreatePasswordHash(string pwd, string salt)
{
    string saltAndPwd = String.Concat(pwd, salt);
    string hashedPwd =
        FormsAuthentication.HashPasswordForStoringInConfigFile(
        saltAndPwd, "sha1");
    return hashedPwd;
}

มีวิธี C # อื่น ๆ สำหรับ hashing รหัสผ่านและเพิ่มเกลือหรือไม่


นี่คือห้องสมุดที่ทำ hashing กับsaltrypto.codeplex.com
Omu

6
คุณควรส่งผ่านขนาดใดในวิธีแรกในการสร้างเกลือ
Shane LeBlanc

6
ลิงก์เสีย
osmanraifgunes

@ShaneLeBlanc คุณควรมีบิตอย่างน้อยที่สุดเท่าที่มีเอาต์พุตของฟังก์ชัน SHA1ไม่ใช่ crypto-grade ดังนั้นคุณควรใช้อย่างน้อยที่สุดSHA256ซึ่งเอาต์พุต 256 บิตหรือ 32 ไบต์ แต่ 256 บิตนั้นไม่สามารถแปลงเป็นฐาน 64 ได้อย่างง่ายดายเพราะแต่ละ base64 ถ่านเข้ารหัส 6 บิตและ 256 ไม่สามารถหารได้ทั้งหมด 6 ดังนั้นคุณต้องมีตัวหารร่วมของ 6 (สำหรับฐาน 64) และ 8 (สำหรับบิตในไบต์) มากกว่า 256 บิตซึ่งเป็น 264 กัดหรือ 33 ไบต์ TLDR: ใช้ 33.
VSO

คำตอบ:


248

จริงๆแล้วนี่เป็นเรื่องแปลกที่มีการแปลงสตริงซึ่งผู้ให้บริการสมาชิกทำเพื่อใส่ลงในไฟล์ปรับแต่ง Hash and salts เป็นแบบไบนารี blobs คุณไม่จำเป็นต้องแปลงเป็นสตริงเว้นแต่ว่าคุณต้องการใส่ลงในไฟล์ข้อความ

ในหนังสือของฉันเริ่มต้นการรักษาความปลอดภัย ASP.NET (โอ้ในที่สุดข้ออ้างที่จะแมงดาหนังสือ) ฉันทำต่อไปนี้

static byte[] GenerateSaltedHash(byte[] plainText, byte[] salt)
{
  HashAlgorithm algorithm = new SHA256Managed();

  byte[] plainTextWithSaltBytes = 
    new byte[plainText.Length + salt.Length];

  for (int i = 0; i < plainText.Length; i++)
  {
    plainTextWithSaltBytes[i] = plainText[i];
  }
  for (int i = 0; i < salt.Length; i++)
  {
    plainTextWithSaltBytes[plainText.Length + i] = salt[i];
  }

  return algorithm.ComputeHash(plainTextWithSaltBytes);            
}

การสร้างเกลือเป็นตัวอย่างในคำถาม Encoding.UTF8.GetBytes(string)คุณสามารถแปลงข้อความให้เป็นอาร์เรย์ไบต์ใช้ หากคุณต้องแปลงแฮชให้เป็นตัวแทนสตริงคุณสามารถใช้Convert.ToBase64StringและConvert.FromBase64Stringแปลงกลับมา

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

public static bool CompareByteArrays(byte[] array1, byte[] array2)
{
  if (array1.Length != array2.Length)
  {
    return false;
  }

  for (int i = 0; i < array1.Length; i++)
  {
    if (array1[i] != array2[i])
    {
      return false;
    }
  }

  return true;
}

มักจะใช้เกลือใหม่ต่อรหัสผ่าน เกลือไม่จำเป็นต้องเก็บเป็นความลับและสามารถเก็บไว้ข้างๆแฮชได้


3
ขอบคุณสำหรับคำแนะนำนี้ - ช่วยฉันจริง ๆ เริ่มต้นใช้งาน ฉันเจอลิงค์นี้ < dijksterhuis.org/creating-salted-hash-values-in-c > ซึ่งฉันพบว่าเป็นคำแนะนำที่ใช้งานได้จริงและสะท้อนสิ่งที่กล่าวไว้ในบทความนี้
Alex P

18
refactor คำสั่งที่ดี LINQ สำหรับ CompareByteArrays return array1.Length == array2.Length && !array1.Where((t, i) => t != array2[i]).Any();
นักล่า

6
@Brettski ในทางเทคนิคแล้วใช่ แต่การมีเกลือเฉพาะตัวสำหรับผู้ใช้แต่ละคนทำให้ Rainbow Tables (ยอมรับกันโดยทั่วไปว่าเป็นวิธีที่มีประสิทธิภาพที่สุดในการถอดรหัสรหัสผ่านที่แฮช) ที่ไม่มีประโยชน์ นี่คือมุมมองแบบย่อที่ให้ภาพรวมเชิงลึก แต่ไม่ครอบคลุมถึงวิธีการจัดเก็บรหัสผ่านอย่างปลอดภัยและสาเหตุ / วิธีการทำงานทั้งหมด
แรนเจอร์

3
@hunter: คุณควรเพิ่ม. ToList () เพื่อให้มีเวลาคงที่ เช่น: return array1.Length == array2.Length &&! array1.Where ((t, i) => t! = array2 [i]) ToList () Any (); อื่น LINQ จะกลับมาทันทีที่พบหนึ่งไบต์ที่ไม่เท่ากัน
Alex Rouillard

17
-1 สำหรับการใช้ฟังก์ชันแฮชอย่างรวดเร็ว ใช้โครงสร้างที่ช้าเช่น PBKDF2, bcrypt หรือ scrypt
CodesInChaos

48

สิ่งที่ blowdart พูด แต่มีรหัสน้อยกว่าเล็กน้อย ใช้ Linq หรือCopyToเพื่อเชื่อมต่ออาร์เรย์

public static byte[] Hash(string value, byte[] salt)
{
    return Hash(Encoding.UTF8.GetBytes(value), salt);
}

public static byte[] Hash(byte[] value, byte[] salt)
{
    byte[] saltedValue = value.Concat(salt).ToArray();
    // Alternatively use CopyTo.
    //var saltedValue = new byte[value.Length + salt.Length];
    //value.CopyTo(saltedValue, 0);
    //salt.CopyTo(saltedValue, value.Length);

    return new SHA256Managed().ComputeHash(saltedValue);
}

Linq มีวิธีที่ง่ายในการเปรียบเทียบอาร์เรย์ไบต์ของคุณด้วย

public bool ConfirmPassword(string password)
{
    byte[] passwordHash = Hash(password, _passwordSalt);

    return _passwordHash.SequenceEqual(passwordHash);
}

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

ด้วยเหตุนี้จึงมีRfc2898DeriveBytesคลาสที่ช้า (และสามารถทำให้ช้าลง) และอาจตอบคำถามตอนที่สองของคำถามเดิมซึ่งสามารถใช้รหัสผ่านและเกลือและส่งคืนแฮช ดูคำถามนี้สำหรับข้อมูลเพิ่มเติม หมายเหตุStack Exchange ใช้Rfc2898DeriveBytesสำหรับการแฮ็นรหัสผ่าน (ซอร์สโค้ดที่นี่ )


6
@MushinNoShin SHA256 เป็นแฮชที่รวดเร็ว การแฮ็นรหัสผ่านต้องแฮชช้าเช่น PBKDF2, bcrypt หรือ scrypt ดูวิธีแฮชรหัสผ่านอย่างปลอดภัยหรือไม่ ใน security.se สำหรับรายละเอียด
CodesInChaos

32

ฉันได้อ่านแล้วว่าฟังก์ชั่นคร่ำเครียดอย่าง SHA256 นั้นไม่ได้มีไว้สำหรับใช้กับการจัดเก็บรหัสผ่าน: https://patrickmn.com/security/storing-passwords-secFE/#notpasswordhashes

ฟังก์ชั่นที่ได้รับมาจากคีย์ที่ปรับเปลี่ยนได้เช่น PBKDF2, bcrypt หรือ scrypt คือ นี่คือ PBKDF2 ที่อิงกับที่ Microsoft เขียนเพื่อPasswordHasherในไลบรารี Microsoft.AspNet.Identity:

/* =======================
 * HASHED PASSWORD FORMATS
 * =======================
 * 
 * Version 3:
 * PBKDF2 with HMAC-SHA256, 128-bit salt, 256-bit subkey, 10000 iterations.
 * Format: { 0x01, prf (UInt32), iter count (UInt32), salt length (UInt32), salt, subkey }
 * (All UInt32s are stored big-endian.)
 */

public string HashPassword(string password)
{
    var prf = KeyDerivationPrf.HMACSHA256;
    var rng = RandomNumberGenerator.Create();
    const int iterCount = 10000;
    const int saltSize = 128 / 8;
    const int numBytesRequested = 256 / 8;

    // Produce a version 3 (see comment above) text hash.
    var salt = new byte[saltSize];
    rng.GetBytes(salt);
    var subkey = KeyDerivation.Pbkdf2(password, salt, prf, iterCount, numBytesRequested);

    var outputBytes = new byte[13 + salt.Length + subkey.Length];
    outputBytes[0] = 0x01; // format marker
    WriteNetworkByteOrder(outputBytes, 1, (uint)prf);
    WriteNetworkByteOrder(outputBytes, 5, iterCount);
    WriteNetworkByteOrder(outputBytes, 9, saltSize);
    Buffer.BlockCopy(salt, 0, outputBytes, 13, salt.Length);
    Buffer.BlockCopy(subkey, 0, outputBytes, 13 + saltSize, subkey.Length);
    return Convert.ToBase64String(outputBytes);
}

public bool VerifyHashedPassword(string hashedPassword, string providedPassword)
{
    var decodedHashedPassword = Convert.FromBase64String(hashedPassword);

    // Wrong version
    if (decodedHashedPassword[0] != 0x01)
        return false;

    // Read header information
    var prf = (KeyDerivationPrf)ReadNetworkByteOrder(decodedHashedPassword, 1);
    var iterCount = (int)ReadNetworkByteOrder(decodedHashedPassword, 5);
    var saltLength = (int)ReadNetworkByteOrder(decodedHashedPassword, 9);

    // Read the salt: must be >= 128 bits
    if (saltLength < 128 / 8)
    {
        return false;
    }
    var salt = new byte[saltLength];
    Buffer.BlockCopy(decodedHashedPassword, 13, salt, 0, salt.Length);

    // Read the subkey (the rest of the payload): must be >= 128 bits
    var subkeyLength = decodedHashedPassword.Length - 13 - salt.Length;
    if (subkeyLength < 128 / 8)
    {
        return false;
    }
    var expectedSubkey = new byte[subkeyLength];
    Buffer.BlockCopy(decodedHashedPassword, 13 + salt.Length, expectedSubkey, 0, expectedSubkey.Length);

    // Hash the incoming password and verify it
    var actualSubkey = KeyDerivation.Pbkdf2(providedPassword, salt, prf, iterCount, subkeyLength);
    return actualSubkey.SequenceEqual(expectedSubkey);
}

private static void WriteNetworkByteOrder(byte[] buffer, int offset, uint value)
{
    buffer[offset + 0] = (byte)(value >> 24);
    buffer[offset + 1] = (byte)(value >> 16);
    buffer[offset + 2] = (byte)(value >> 8);
    buffer[offset + 3] = (byte)(value >> 0);
}

private static uint ReadNetworkByteOrder(byte[] buffer, int offset)
{
    return ((uint)(buffer[offset + 0]) << 24)
        | ((uint)(buffer[offset + 1]) << 16)
        | ((uint)(buffer[offset + 2]) << 8)
        | ((uint)(buffer[offset + 3]));
}

หมายเหตุสิ่งนี้ต้องใช้Microsoft.AspNetCore.Cryptography.KeyDerivationแพคเกจ nuget ที่ติดตั้งซึ่งต้องใช้. NET Standard 2.0 (.NET 4.6.1 หรือสูงกว่า) สำหรับ. NET รุ่นก่อนหน้าดูคลาสCryptoจากไลบรารี System.Web.Helpers ของ Microsoft

อัปเดตพฤศจิกายน 2558
อัปเดตคำตอบเพื่อใช้การใช้งานจากห้องสมุด Microsoft อื่นซึ่งใช้การแฮช PBKDF2-HMAC-SHA256 แทน PBKDF2-HMAC-SHA1 (โน้ต PBKDF2-HMAC-SHA1 ยังคงปลอดภัยหาก iterCount สูงพอ) คุณสามารถตรวจสอบแหล่งที่มาของรหัสที่ง่าย ๆ ที่ถูกคัดลอกจากที่จริงแล้วมันจัดการการตรวจสอบและการอัพเกรดแฮชที่นำมาใช้จากคำตอบก่อนหน้านี้มีประโยชน์ถ้าคุณต้องการเพิ่ม iterCount ในอนาคต


1
หมายเหตุมันอาจจะคุ้มค่าที่เพิ่มขึ้น PBKDF2IterCount ไปเป็นจำนวนที่สูงขึ้นเห็นsecurity.stackexchange.com/q/3959มาก
Michael

2
1) ลดPBKDF2SubkeyLengthเหลือ 20 ไบต์ นั่นคือขนาดที่เป็นธรรมชาติ f SHA1 และเพิ่มขึ้นเกินกว่านั้นจะทำให้ผู้พิทักษ์ช้าลงโดยไม่ทำให้ผู้โจมตีช้าลง 2) ฉันแนะนำให้เพิ่มจำนวนการทำซ้ำ ฉันแนะนำ 10k ถึง 100k ขึ้นอยู่กับงบประมาณประสิทธิภาพของคุณ 3) การเปรียบเทียบเวลาคงที่จะไม่เจ็บเช่นกัน แต่ไม่มีผลกระทบเชิงปฏิบัติมากนัก
CodesInChaos

KeyDerivationPrf, KeyDerivation และ BlockCopy ไม่ได้กำหนดไว้คลาสของพวกเขาคืออะไร?
mrbengi

@mrbengi คุณติดตั้งแพคเกจ nuget ของ Microsoft.AspNet.Cryptography.KeyDerivation แล้วหรือยัง? หากไม่เหมาะสมที่นี่เป็นรุ่นที่ไม่ต้องใช้แพคเกจ nuget Buffer.BlockCopy ควรมีอยู่เป็นส่วนหนึ่งของ System
Michael

1
แพ็คเกจ nuget คือ Microsoft.AspNetCore.Cryptography.KeyDerivation
James Blake

25

เกลือถูกใช้เพื่อเพิ่มระดับความซับซ้อนพิเศษให้กับแฮชเพื่อให้ยากต่อการแตกร้าวโดยใช้กำลัง

จากบทความใน Sitepoint :

แฮกเกอร์ยังคงสามารถทำสิ่งที่เรียกว่าการโจมตีพจนานุกรม ฝ่ายที่เป็นอันตรายอาจทำการโจมตีด้วยพจนานุกรมโดยรับรหัสผ่าน 100,000 รายการที่พวกเขารู้ว่าผู้คนใช้บ่อยๆ (เช่นชื่อเมืองทีมกีฬา ฯลฯ ) แฮชพวกเขาแล้วเปรียบเทียบแต่ละรายการในพจนานุกรมกับแต่ละแถวในฐานข้อมูล ตาราง. หากแฮ็กเกอร์หาคู่บิงโก! พวกเขามีรหัสผ่านของคุณ อย่างไรก็ตามเพื่อแก้ปัญหานี้เราต้องการเพียงเกลือกัญชาเท่านั้น

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

ในสถานการณ์นี้แฮ็คเกอร์ไม่เพียง แต่ต้องเดารหัสผ่านเท่านั้น แต่พวกเขาต้องเดาด้วยเช่นกัน การใส่เกลือลงในข้อความที่ชัดเจนช่วยเพิ่มความปลอดภัย: ตอนนี้ถ้าแฮ็กเกอร์พยายามโจมตีพจนานุกรมเขาจะต้องแฮช 100,000 รายการของเขาด้วยเกลือของผู้ใช้ทุกแถว แม้ว่ามันจะเป็นไปได้ แต่โอกาสในการแฮ็คที่ประสบความสำเร็จก็ลดลงอย่างรุนแรง

ไม่มีวิธีการทำเช่นนี้โดยอัตโนมัติใน. NET ดังนั้นคุณจะได้แก้ปัญหาข้างต้น


เกลือถูกใช้เพื่อป้องกันสิ่งต่าง ๆ เช่นตารางสายรุ้ง เพื่อป้องกันการโจมตีจากพจนานุกรมจำเป็นต้องมีปัจจัยการทำงาน (หรือที่รู้จักกันในชื่อการยืดกุญแจ) เช่น KDF ที่ดี: en.wikipedia.org/wiki/Key_stretching
Erwan Legrand

11

ฉันสร้างคลาสที่มีวิธีการดังต่อไปนี้:

  1. สร้างเกลือ
  2. Hash Input
  3. ตรวจสอบอินพุต

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

    `



3

ฉันได้สร้างไลบรารีSimpleHashing.Netเพื่อให้กระบวนการแฮชทำได้ง่ายด้วยคลาสพื้นฐานที่ Microsoft เตรียมไว้ให้ SHA ทั่วไปไม่เพียงพอที่จะเก็บรหัสผ่านไว้อย่างปลอดภัยอีกต่อไป

ห้องสมุดใช้แนวคิดของรูปแบบแฮชจาก Bcrypt แต่เนื่องจากไม่มีการนำ MS ไปใช้อย่างเป็นทางการฉันชอบที่จะใช้สิ่งที่มีอยู่ในกรอบงาน (เช่น PBKDF2) แต่มันค่อนข้างยากเกินไปสำหรับกล่อง

นี่คือตัวอย่างย่อเกี่ยวกับวิธีใช้ไลบรารี:

ISimpleHash simpleHash = new SimpleHash();

// Creating a user hash, hashedPassword can be stored in a database
// hashedPassword contains the number of iterations and salt inside it similar to bcrypt format
string hashedPassword = simpleHash.Compute("Password123");

// Validating user's password by first loading it from database by username
string storedHash = _repository.GetUserPasswordHash(username);
isPasswordValid = simpleHash.Verify("Password123", storedHash);

2

นี่คือวิธีที่ฉันทำ .. ฉันสร้างแฮชและเก็บไว้โดยใช้ProtectedDataapi:

    public static string GenerateKeyHash(string Password)
    {
        if (string.IsNullOrEmpty(Password)) return null;
        if (Password.Length < 1) return null;

        byte[] salt = new byte[20];
        byte[] key = new byte[20];
        byte[] ret = new byte[40];

        try
        {
            using (RNGCryptoServiceProvider randomBytes = new RNGCryptoServiceProvider())
            {
                randomBytes.GetBytes(salt);

                using (var hashBytes = new Rfc2898DeriveBytes(Password, salt, 10000))
                {
                    key = hashBytes.GetBytes(20);
                    Buffer.BlockCopy(salt, 0, ret, 0, 20);
                    Buffer.BlockCopy(key, 0, ret, 20, 20);
                }
            }
            // returns salt/key pair
            return Convert.ToBase64String(ret);
        }
        finally
        {
            if (salt != null)
                Array.Clear(salt, 0, salt.Length);
            if (key != null)
                Array.Clear(key, 0, key.Length);
            if (ret != null)
                Array.Clear(ret, 0, ret.Length);
        } 
    }

    public static bool ComparePasswords(string PasswordHash, string Password)
    {
        if (string.IsNullOrEmpty(PasswordHash) || string.IsNullOrEmpty(Password)) return false;
        if (PasswordHash.Length < 40 || Password.Length < 1) return false;

        byte[] salt = new byte[20];
        byte[] key = new byte[20];
        byte[] hash = Convert.FromBase64String(PasswordHash);

        try
        {
            Buffer.BlockCopy(hash, 0, salt, 0, 20);
            Buffer.BlockCopy(hash, 20, key, 0, 20);

            using (var hashBytes = new Rfc2898DeriveBytes(Password, salt, 10000))
            {
                byte[] newKey = hashBytes.GetBytes(20);

                if (newKey != null)
                    if (newKey.SequenceEqual(key))
                        return true;
            }
            return false;
        }
        finally
        {
            if (salt != null)
                Array.Clear(salt, 0, salt.Length);
            if (key != null)
                Array.Clear(key, 0, key.Length);
            if (hash != null)
                Array.Clear(hash, 0, hash.Length);
        }
    }

    public static byte[] DecryptData(string Data, byte[] Salt)
    {
        if (string.IsNullOrEmpty(Data)) return null;

        byte[] btData = Convert.FromBase64String(Data);

        try
        {
            return ProtectedData.Unprotect(btData, Salt, DataProtectionScope.CurrentUser);
        }
        finally
        {
            if (btData != null)
                Array.Clear(btData, 0, btData.Length);
        }
    }

    public static string EncryptData(byte[] Data, byte[] Salt)
    {
        if (Data == null) return null;
        if (Data.Length < 1) return null;

        byte[] buffer = new byte[Data.Length];

        try
        {
            Buffer.BlockCopy(Data, 0, buffer, 0, Data.Length);
            return System.Convert.ToBase64String(ProtectedData.Protect(buffer, Salt, DataProtectionScope.CurrentUser));
        }
        finally
        {
            if (buffer != null)
                Array.Clear(buffer, 0, buffer.Length);
        }
    }

ฉันจะเรียกมันในขณะที่บันทึกและเมื่อเปรียบเทียบในภายหลังได้อย่างไร
SearchForKnowledge

2

ผมอ่านทุกคำตอบและฉันคิดว่าพอเหล่านั้นเป็นพิเศษ@Michaelบทความที่มีคร่ำเครียดช้าและ@CodesInChaosความคิดเห็นที่ดี แต่ผมตัดสินใจที่จะแบ่งปันข้อมูลโค้ดของฉันสำหรับคร่ำเครียด / การตรวจสอบที่อาจเป็นประโยชน์และมันไม่จำเป็นต้อง [ Microsoft.AspNet.Cryptography .KeyDerivation ]

    private static bool SlowEquals(byte[] a, byte[] b)
            {
                uint diff = (uint)a.Length ^ (uint)b.Length;
                for (int i = 0; i < a.Length && i < b.Length; i++)
                    diff |= (uint)(a[i] ^ b[i]);
                return diff == 0;
            }

    private static byte[] PBKDF2(string password, byte[] salt, int iterations, int outputBytes)
            {
                Rfc2898DeriveBytes pbkdf2 = new Rfc2898DeriveBytes(password, salt);
                pbkdf2.IterationCount = iterations;
                return pbkdf2.GetBytes(outputBytes);
            }

    private static string CreateHash(string value, int salt_bytes, int hash_bytes, int pbkdf2_iterations)
            {
                // Generate a random salt
                RNGCryptoServiceProvider csprng = new RNGCryptoServiceProvider();
                byte[] salt = new byte[salt_bytes];
                csprng.GetBytes(salt);

                // Hash the value and encode the parameters
                byte[] hash = PBKDF2(value, salt, pbkdf2_iterations, hash_bytes);

                //You need to return the salt value too for the validation process
                return Convert.ToBase64String(hash) + ":" + 
                       Convert.ToBase64String(hash);
            }

    private static bool ValidateHash(string pureVal, string saltVal, string hashVal, int pbkdf2_iterations)
            {
                try
                {
                    byte[] salt = Convert.FromBase64String(saltVal);
                    byte[] hash = Convert.FromBase64String(hashVal);

                    byte[] testHash = PBKDF2(pureVal, salt, pbkdf2_iterations, hash.Length);
                    return SlowEquals(hash, testHash);
                }
                catch (Exception ex)
                {
                    return false;
                }
            }

โปรดให้ความสนใจฟังก์ชั่น SlowEquals ที่สำคัญมากในที่สุดฉันหวังว่าความช่วยเหลือนี้และโปรดอย่าลังเลที่จะแนะนำวิธีการที่ดีกว่าให้ฉัน


แทนที่จะสร้างการวนรอบไม่ว่างทำไมไม่ลองใส่การหน่วงเวลาแบบไม่ยุ่งเทียม เช่นใช้ Task.Delay สิ่งนี้จะหน่วงเวลาความพยายามเดรัจฉาน แต่ไม่บล็อกเธรดที่ใช้งานอยู่
gburton

@ gburton ขอบคุณสำหรับคำแนะนำของคุณ ฉันจะตรวจสอบมัน.
QMaster

มีการพิมพ์ผิดใน CreateHash: คุณกำลังเชื่อมต่อ Convert.ToBase64String (hash) กับตัวเองแทนที่จะเป็นเกลือ นอกเหนือจากนั้นนี่เป็นคำตอบที่ดีที่มีปัญหาในทุกความคิดเห็นเกี่ยวกับคำตอบอื่น ๆ
ZeRemz

2

ใช้System.Web.Helpers.Cryptoแพ็คเกจ NuGet จาก Microsoft มันเพิ่มเกลือโดยอัตโนมัติในแฮช

คุณแฮรหัสผ่านเช่นนี้: var hash = Crypto.HashPassword("foo");

คุณยืนยันรหัสผ่านเช่นนี้: var verified = Crypto.VerifyHashedPassword(hash, "foo");


1

หากคุณไม่ใช้ asp.net หรือ. net core นอกจากนี้ยังมีวิธีที่ง่ายในโครงการ = = .Net Standard 2.0

ก่อนอื่นคุณสามารถตั้งค่าขนาดที่ต้องการของหมายเลขแฮชเกลือและการวนซ้ำซึ่งสัมพันธ์กับระยะเวลาของการสร้างแฮช:

private const int SaltSize = 32;
private const int HashSize = 32;
private const int IterationCount = 10000;

เพื่อสร้างแฮชของรหัสผ่านและเกลือคุณสามารถใช้สิ่งนี้:

public static string GeneratePasswordHash(string password, out string salt)
{
    using (Rfc2898DeriveBytes rfc2898DeriveBytes = new Rfc2898DeriveBytes(password, SaltSize))
    {
        rfc2898DeriveBytes.IterationCount = IterationCount;
        byte[] hashData = rfc2898DeriveBytes.GetBytes(HashSize);
        byte[] saltData = rfc2898DeriveBytes.Salt;
        salt = Convert.ToBase64String(saltData);
        return Convert.ToBase64String(hashData);
    }
}

ในการตรวจสอบว่ารหัสผ่านที่ผู้ใช้ป้อนนั้นถูกต้องคุณสามารถตรวจสอบกับค่าในฐานข้อมูลของคุณ:

public static bool VerifyPassword(string password, string passwordHash, string salt)
{
    using (Rfc2898DeriveBytes rfc2898DeriveBytes = new Rfc2898DeriveBytes(password, SaltSize))
    {
        rfc2898DeriveBytes.IterationCount = IterationCount;
        rfc2898DeriveBytes.Salt = Convert.FromBase64String(salt);
        byte[] hashData = rfc2898DeriveBytes.GetBytes(HashSize);
        return Convert.ToBase64String(hashData) == passwordHash;
    }
}

การทดสอบหน่วยต่อไปนี้แสดงการใช้งาน:

string password = "MySecret";

string passwordHash = PasswordHasher.GeneratePasswordHash(password, out string salt);

Assert.True(PasswordHasher.VerifyPassword(password, passwordHash, salt));
Assert.False(PasswordHasher.VerifyPassword(password.ToUpper(), passwordHash, salt));

แหล่ง Microsoft Rfc2898DeriveBytes


-1

ในการตอบคำถามนี้ในส่วนเดิมของ "มีวิธีการ C # อื่น ๆ สำหรับแฮ็ชรหัสผ่าน" คุณสามารถทำได้โดยใช้ ASP.NET Identity v3.0 https://www.nuget.org/packages/Microsoft.AspNet.Identity EntityFramework / 3.0.0-RC1 สุดท้าย

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNet.Identity;
using System.Security.Principal;

namespace HashTest{


    class Program
    {
        static void Main(string[] args)
        {

            WindowsIdentity wi = WindowsIdentity.GetCurrent();

            var ph = new PasswordHasher<WindowsIdentity>();

            Console.WriteLine(ph.HashPassword(wi,"test"));

            Console.WriteLine(ph.VerifyHashedPassword(wi,"AQAAAAEAACcQAAAAEA5S5X7dmbx/NzTk6ixCX+bi8zbKqBUjBhID3Dg1teh+TRZMkAy3CZC5yIfbLqwk2A==","test"));

        }
    }


}

-1
 protected void m_GenerateSHA256_Button1_Click(objectSender, EventArgs e)
{
string salt =createSalt(10);
string hashedPassword=GenerateSHA256Hash(m_UserInput_TextBox.Text,Salt);
m_SaltHash_TextBox.Text=Salt;
 m_SaltSHA256Hash_TextBox.Text=hashedPassword;

}
 public string createSalt(int size)
{
 var rng= new System.Security.Cyptography.RNGCyptoServiceProvider();
 var buff= new byte[size];
rng.GetBytes(buff);
 return Convert.ToBase64String(buff);
}


 public string GenerateSHA256Hash(string input,string salt)
{
 byte[]bytes=System.Text.Encoding.UTF8.GetBytes(input+salt);
 new System.Security.Cyptography.SHA256Managed();
 byte[]hash=sha256hashString.ComputedHash(bytes);
 return bytesArrayToHexString(hash);
  }

วิธีอื่นคือรหัสผ่านสตริง = HashPasswordForStoringInConfigFile (TextBox1.Text, SHA1)
ankush shukla

-6
create proc [dbo].[hash_pass] @family nvarchar(50), @username nvarchar(50), @pass nvarchar(Max),``` @semat nvarchar(50), @tell nvarchar(50)

as insert into tbl_karbar values (@family,@username,(select HASHBYTES('SHA1' ,@pass)),@semat,@tell)
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.