web-dev-qa-db-fra.com

Mots de passe de hachage et de sel en C #

Je venais de lire l'un des articles de DavidHayden sur Hashing User Passwords .

Vraiment, je ne peux pas obtenir ce qu'il essaie de réaliser.

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

Existe-t-il une autre méthode C # pour hacher les mots de passe et y ajouter du sel?

170
ACP

En fait, c'est assez étrange, avec les conversions de chaînes - que le fournisseur d'appartenance fait pour les mettre dans des fichiers de configuration. Les hachages et les sels sont des blobs binaires, vous n'avez pas besoin de les convertir en chaînes à moins de vouloir les mettre dans des fichiers texte.

Dans mon livre, débutant avec ASP.NET Security , (oh, enfin, une excuse pour bichonner le livre), je fais ce qui suit

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

La génération de sel est comme l'exemple dans la question. Vous pouvez convertir du texte en tableaux d'octets à l'aide de Encoding.UTF8.GetBytes(string). Si vous devez convertir un hachage en représentation de chaîne, vous pouvez utiliser Convert.ToBase64String et Convert.FromBase64String pour le reconvertir.

Notez que vous ne pouvez pas utiliser l'opérateur d'égalité sur les tableaux d'octets, il vérifie les références et vous devez donc simplement parcourir les deux tableaux en vérifiant chaque octet ainsi.

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

Toujours utilisez un nouveau sel par mot de passe. Les sels ne doivent pas être gardés secrets et peuvent être stockés à côté du hash lui-même.

238
blowdart

Ce que blowdart a dit, mais avec un peu moins de code. Utilisez Linq ou CopyTo pour concaténer des tableaux.

_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 dispose également d’un moyen simple de comparer vos tableaux d’octets.

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

    return _passwordHash.SequenceEqual(passwordHash);
}
_

Avant de mettre en œuvre tout cela cependant, consultez ce post . Pour le hachage de mot de passe, vous souhaiterez peut-être un algorithme de hachage lent, pas rapide.

À cette fin, il y a la classe Rfc2898DeriveBytes qui est lente (et peut être ralentie), et peut répondre à la deuxième partie de la question initiale en ce sens qu'elle peut prendre un mot de passe et saler et retourne un hash. Voir cette question pour plus d'informations. Remarque: Stack Exchange utilise _Rfc2898DeriveBytes_ pour le hachage du mot de passe (code source ici ).

43
Adam Boddington

J'ai lu que des fonctions de hachage telles que SHA256 n'étaient pas vraiment destinées à stocker des mots de passe: https://patrickmn.com/security/storing-passwords-securely/#notpasswordhashes

À la place, les fonctions de dérivation de clé adaptatives telles que PBKDF2, bcrypt ou scrypt étaient utilisées. Voici un fichier basé sur PBKDF2 pour lequel Microsoft a écrit PasswordHasher dans leur bibliothèque 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]));
}

Notez que cela nécessite Microsoft.AspNetCore.Cryptography.KeyDerivation Le paquet nuget est installé et requiert .NET Standard 2.0 (.NET 4.6.1 ou version ultérieure). Pour les versions antérieures de .NET, consultez la classe Crypto de la bibliothèque System.Web.Helpers de Microsoft.

Mise à jour novembre 2015
Réponse mise à jour pour utiliser une implémentation d'une autre bibliothèque Microsoft utilisant le hachage PBKDF2-HMAC-SHA256 à la place de PBKDF2-HMAC-SHA1 (note PBKDF2-HMAC-SHA1 est toujours sécurisé si iterCount est assez haut). Vous pouvez consulter le source le code simplifié a été copié car il gère réellement la validation et la mise à niveau des hachages mis en œuvre à partir de la réponse précédente, ce qui est utile si vous devez augmenter le nombre de iterCount ultérieurement.

30
Michael

Le sel est utilisé pour ajouter un niveau supplémentaire de complexité au hachage, pour le rendre plus difficile à craquer par la force brute.

D'un article sur Sitepoint :

Un pirate informatique peut toujours effectuer ce qu’on appelle une attaque par dictionnaire. Les parties malveillantes peuvent lancer une attaque par dictionnaire en utilisant, par exemple, 100 000 mots de passe qu’elles connaissent bien, tels que les noms de villes, les équipes sportives, etc.), les hacher, puis comparer chaque entrée du dictionnaire avec chaque ligne de la base de données. table. Si les pirates trouvent une correspondance, bingo! Ils ont ton mot de passe. Pour résoudre ce problème, cependant, il suffit de saler le hasch.

Pour saler un hachage, nous obtenons simplement une chaîne de texte d'aspect aléatoire, nous la concaténons avec le mot de passe fourni par l'utilisateur, puis nous hachons la chaîne générée de manière aléatoire et le mot de passe en une seule valeur. Nous sauvegardons ensuite le hachage et le sel en tant que champs distincts dans la table Utilisateurs.

Dans ce scénario, non seulement un pirate informatique aurait besoin de deviner le mot de passe, mais également le sel. Ajouter du sel au texte en clair améliore la sécurité: désormais, si un pirate informatique tente une attaque par dictionnaire, il doit hacher ses 100 000 entrées avec le sel de chaque ligne de l'utilisateur. Bien que cela soit encore possible, les chances de succès du piratage informatique diminuent radicalement.

Il n'y a pas de méthode qui fait automatiquement cela dans .NET, vous aurez donc à aller avec la solution précédente.

24
Seb Nilsson

J'ai créé une classe qui a la méthode suivante:

  1. Créer du sel
  2. Entrée de hachage
  3. Valider la saisie

    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, c'est mieux! http://sourceforge.net/projects/pwdtknet/ et c'est mieux car ..... il exécute Key Stretching AND utilise HMACSHA512 :)

5
thashiznets

J'ai créé une bibliothèque SimpleHashing.Net pour faciliter le processus de hachage avec les classes de base fournies par Microsoft. Un SHA ordinaire _ n'est pas vraiment suffisant pour que les mots de passe soient stockés de manière sécurisée.

La bibliothèque utilise l’idée du format de hachage de Bcrypt, mais comme il n’ya pas d’implémentation MS officielle, je préfère utiliser ce qui est disponible dans le framework (par exemple, PBKDF2), mais c’est un peu trop compliqué.

Voici un exemple rapide d'utilisation de la bibliothèque:

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
Ilya Chernomordik

Voici comment je le fais. Je crée le hachage et le stocke à l'aide de 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

J'ai lu toutes les réponses et je pense que ça suffit, spécialement @ Michael articles avec hachage lente et @ CodesInChaos bons commentaires, mais j'ai décidé de partager mon extrait de code pour le hachage/la validation peut être utile et ne nécessite pas [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;
                }
            }

S'il vous plaît prêter attention fonction SlowEquals qui est si important, Enfin, j'espère que cette aide et S'il vous plaît n'hésitez pas à me conseiller sur de meilleures approches.

1
QMaster

Utilisez le package System.Web.Helpers.Crypto NuGet de Microsoft. Il ajoute automatiquement du sel au hash.

Vous devez hacher un mot de passe comme ceci: var hash = Crypto.HashPassword("foo");

Vous vérifiez un mot de passe comme ceci: 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