it-swarm.com.de

Hash- und Salt-Passwörter in C #

Ich habe gerade einen Artikel von DavidHayden über Hashing User Passwords gelesen.

Wirklich, ich kann nicht verstehen, was er erreichen will.

Hier ist sein Code:

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

Gibt es eine andere C # -Methode zum Hashing von Kennwörtern und zum Hinzufügen von Salt?

170
ACP

Eigentlich ist das seltsam bei den String-Konvertierungen, die der Mitgliedschaftsanbieter vornimmt, um sie in Konfigurationsdateien zu speichern. Hashes und Saltes sind binäre Blobs. Sie müssen sie nicht in Zeichenfolgen konvertieren, es sei denn, Sie möchten sie in Textdateien speichern.

In meinem Buch Beginning ASP.NET Security (oh, endlich eine Ausrede, um das Buch zu pimpen) mache ich Folgendes

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

Die Salzgewinnung ist als Beispiel in Frage. Mit Encoding.UTF8.GetBytes(string) können Sie Text in Byte-Arrays konvertieren. Wenn Sie einen Hash in seine Zeichenfolgendarstellung konvertieren müssen, können Sie ihn mit Convert.ToBase64String Und Convert.FromBase64String Zurückkonvertieren.

Sie sollten beachten, dass Sie den Gleichheitsoperator nicht für Bytearrays verwenden können, er prüft Referenzen und Sie sollten einfach beide Arrays durchlaufen, um jedes Byte auf diese Weise zu überprüfen

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

Verwenden Sie immer ein neues Salz pro Passwort. Salze müssen nicht geheim gehalten werden und können neben dem Hash selbst aufbewahrt werden.

238
blowdart

Was Blowdart gesagt hat, aber mit etwas weniger Code. Verwenden Sie Linq oder CopyTo, um Arrays zu verketten.

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

Mit Linq können Sie auch Ihre Bytearrays auf einfache Weise vergleichen.

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

    return _passwordHash.SequenceEqual(passwordHash);
}

Bevor Sie dies jedoch implementieren, lesen Sie diesen Beitrag . Für das Hashing von Passwörtern möchten Sie möglicherweise einen langsamen Hash-Algorithmus, keinen schnellen.

Zu diesem Zweck gibt es die Rfc2898DeriveBytes Klasse, die langsam ist (und langsamer gemacht werden kann) und den zweiten Teil der ursprünglichen Frage dadurch beantworten kann, dass sie ein Passwort und Salt nehmen und einen Hash zurückgeben kann. Siehe diese Frage für weitere Informationen. Beachten Sie, Stack Exchange verwendet Rfc2898DeriveBytes für Passwort-Hashing (Quellcode hier ).

43
Adam Boddington

Ich habe gelesen, dass Hashing-Funktionen wie SHA256 nicht für die Speicherung von Passwörtern vorgesehen sind: https://patrickmn.com/security/storing-passwords-securely/#notpasswordhashes

Stattdessen wurden adaptive Schlüsselableitungsfunktionen wie PBKDF2, bcrypt oder scrypt verwendet. Hier ist eine PBKDF2-basierte Version, die Microsoft für PasswordHasher in der Microsoft.AspNet.Identity-Bibliothek geschrieben hat:

/* =======================
 * 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]));
}

Beachten Sie, dass hierfür Microsoft.AspNetCore.Cryptography.KeyDerivation ein Nuget-Paket installiert sein muss, für das .NET Standard 2.0 (.NET 4.6.1 oder höher) erforderlich ist. Frühere Versionen von .NET finden Sie in der Klasse Crypto der Microsoft-Bibliothek System.Web.Helpers.

Update November 2015
Aktualisierte Antwort zur Verwendung einer Implementierung aus einer anderen Microsoft-Bibliothek, die PBKDF2-HMAC-SHA256-Hashing anstelle von PBKDF2-HMAC-SHA1 verwendet (Anmerkung: PBKDF2-HMAC-SHA1 ist immer noch sicher if iterCount ist hoch genug). Sie können das Quelle auschecken, aus dem der vereinfachte Code kopiert wurde, da er das Validieren und Aktualisieren von Hashes handhabt, die aus der vorherigen Antwort implementiert wurden.

30
Michael

Salz wird verwendet, um dem Hash einen zusätzlichen Komplexitätsgrad zu verleihen und das Knacken mit Brute-Force-Angriffen zu erschweren.

Aus einem Artikel auf Sitepoint :

Ein Hacker kann immer noch so etwas wie einen Wörterbuchangriff ausführen. Böswillige Parteien können einen Wörterbuchangriff ausführen, indem sie beispielsweise 100.000 Kennwörter verwenden, von denen sie wissen, dass sie häufig verwendet werden (z. B. Städtenamen, Sportmannschaften usw.), diese mit einem Hash versehen und dann jeden Eintrag im Wörterbuch mit jeder Zeile in der Datenbank vergleichen Tabelle. Wenn die Hacker eine Übereinstimmung finden, Bingo! Sie haben Ihr Passwort. Um dieses Problem zu lösen, müssen wir jedoch nur den Hash salzen.

Um einen Hash zu salzen, erstellen wir einfach eine zufällig aussehende Textzeichenfolge, verknüpfen sie mit dem vom Benutzer angegebenen Kennwort und fassen dann sowohl die zufällig generierte Zeichenfolge als auch das Kennwort als einen Wert zusammen. Wir speichern dann sowohl den Hash als auch das Salt als separate Felder in der Users-Tabelle.

In diesem Szenario müsste ein Hacker nicht nur das Passwort erraten, sondern auch das Salz. Das Hinzufügen von Salz zum Klartext erhöht die Sicherheit: Wenn ein Hacker einen Wörterbuchangriff versucht, muss er seine 100.000 Einträge mit dem Salz jeder Benutzerzeile hacken. Obwohl es immer noch möglich ist, verringern sich die Chancen, Erfolg zu haben, radikal.

In .NET gibt es keine Methode, die dies automatisch ausführt. Sie müssen sich also für die obige Lösung entscheiden.

24
Seb Nilsson

Ich habe eine Klasse mit der folgenden Methode erstellt:

  1. Salz herstellen
  2. Hash-Eingabe
  3. Eingabe validieren

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

    `

8
Bamidele Alegbe

Bah, das ist besser! http://sourceforge.net/projects/pwdtknet/ und es ist besser, weil ..... es führt Key Stretching AND verwendet HMACSHA512 :)

5
thashiznets

Ich habe eine Bibliothek erstellt SimpleHashing.Net , um den Prozess des Hashens mit von Microsoft bereitgestellten Basisklassen zu vereinfachen. Normal SHA ist nicht mehr ausreichend, um Passwörter sicher zu speichern.

Die Bibliothek verwendet die Idee des Hash-Formats von Bcrypt, aber da es keine offizielle MS-Implementierung gibt, bevorzuge ich die Verwendung der im Framework verfügbaren Inhalte (d. H. PBKDF2), aber es ist ein bisschen zu schwierig.

Dies ist ein kurzes Beispiel für die Verwendung der Bibliothek:

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

So mache ich das .. Ich erstelle den Hash und speichere ihn mit der ProtectedData-API:

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

Ich habe alle Antworten gelesen und denke, dass diese genug sind, insbesondere @ Michael Artikel mit langsamem Hashing und @ CodesInChaos gute Kommentare, aber ich habe beschlossen, mein Code-Snippet für das Hashing/Validieren des Codes freizugeben kann nützlich sein und erfordert keine [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;
                }
            }

Bitte beachten Sie die SlowEquals-Funktion, die so wichtig ist. Schließlich hoffe ich, dass diese Hilfe und Bitte zögern Sie nicht, mich über bessere Ansätze zu beraten.

1
QMaster

Verwenden Sie das NuGet-Paket System.Web.Helpers.Crypto Von Microsoft. Es wird automatisch Salz zum Hash hinzugefügt.

Sie haben ein Passwort wie dieses: var hash = Crypto.HashPassword("foo");

Sie verifizieren ein Passwort wie dieses: var verified = Crypto.VerifyHashedPassword(hash, "foo");

1
Kai Hartmann
 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);
  }
0
ankush shukla