208 votes

Mots de passe avec hachage et salage en C#

J'étais juste en train de parcourir un des articles de David Hayden sur Hachage des mots de passe des utilisateurs .

Je ne comprends vraiment pas ce qu'il essaie de faire.

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 en C# pour hacher les mots de passe et y ajouter du sel ?

1 votes

Voici une bibliothèque qui effectue le hachage avec du sel encrypto.codeplex.com

6 votes

Que devez-vous passer pour la taille dans la première méthode pour générer du sel ?

9 votes

Le lien est rompu.

265voto

blowdart Points 28735

En fait, c'est plutôt étrange, avec les conversions de chaînes de caractères - que le fournisseur d'adhésion fait pour les mettre dans les fichiers de configuration. Les hachages et les sels sont des blobs binaires, vous n'avez pas besoin de les convertir en chaînes de caractères, sauf si vous voulez les placer dans des fichiers texte.

Dans mon livre, Début de la sécurité ASP.NET (oh enfin, une excuse pour vanter les mérites du 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 la fonction Encoding.UTF8.GetBytes(string) . Si vous devez convertir un hachage en sa représentation sous forme de chaîne, vous pouvez utiliser Convert.ToBase64String et Convert.FromBase64String pour le reconvertir.

Vous devez noter 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 boucler à travers 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 utiliser un nouveau sel par mot de passe. Les sels n'ont pas besoin d'être gardés secrets et peuvent être stockés avec le hachage lui-même.

3 votes

Merci pour ces conseils - ils m'ont vraiment aidé à démarrer. Je suis également tombé sur ce lien < dijksterhuis.org/creating-salted-hash-values-in-c > J'ai trouvé que c'était un bon conseil pratique et que cela reflétait une grande partie de ce qui a été dit dans ce message.

20 votes

Refactor d'instructions LINQ pour CompareByteArrays return array1.Length == array2.Length && !array1.Where((t, i) => t != array2[i]).Any();

0 votes

On pourrait être un peu plus efficace et plus court en utilisant 'Array.Copy' pour copier le texte brut et le sel dans le nouveau tableau.

54voto

Adam Boddington Points 3438

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

Cependant, avant de mettre en œuvre tout ceci, consultez ce poste . Pour le hachage d'un mot de passe, il est préférable d'utiliser un algorithme de hachage lent, et non rapide.

À cette fin, il existe le Rfc2898DeriveBytes qui est lente (et peut être rendue plus lente), et peut répondre à la deuxième partie de la question originale en ce qu'elle peut prendre un mot de passe et un sel et retourner un hachage. Voir cette question pour plus d'informations. Note, Stack Exchange utilise Rfc2898DeriveBytes pour le hachage des mots de passe (code source ici ).

7 votes

@MushinNoShin SHA256 est un hachage rapide. Le hachage de mot de passe nécessite un hachage lent, comme PBKDF2, bcrypt ou scrypt. Voir Comment hacher les mots de passe en toute sécurité ? sur security.se pour plus de détails.

38voto

Michael Points 1971

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

Au lieu de cela, des fonctions de dérivation de clés adaptatives comme PBKDF2, bcrypt ou scrypt ont été utilisées. Voici une fonction basée sur PBKDF2 que Microsoft a écrit pour 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 paquet nuget installé qui nécessite .NET Standard 2.0 (.NET 4.6.1 ou supérieur). Pour les versions antérieures de .NET, voir le Crypto de la bibliothèque System.Web.Helpers de Microsoft.

Mise à jour Nov 2015
Mise à jour de la réponse pour utiliser une implémentation d'une bibliothèque Microsoft différente qui utilise le hachage PBKDF2-HMAC-SHA256 au lieu de PBKDF2-HMAC-SHA1 (notez que PBKDF2-HMAC-SHA1 est toujours en sécurité si iterCount est suffisamment élevé). Vous pouvez consulter le source le code simplifié a été copié à partir de car il gère effectivement la validation et la mise à jour des hachages implémentés à partir de la réponse précédente, utile si vous avez besoin d'augmenter iterCount dans le futur.

1 votes

Notez qu'il peut être intéressant d'augmenter PBKDF2IterCount à un nombre plus élevé, voir security.stackexchange.com/q/3959 pour plus.

3 votes

1) Réduire PBKDF2SubkeyLength à 20 octets. C'est la taille naturelle de SHA1 et l'augmenter au-delà ralentit le défenseur sans ralentir l'attaquant. 2) Je recommande d'augmenter le nombre d'itérations. Je recommande 10k à 100k en fonction de votre budget de performance. 3) Une comparaison en temps constant ne ferait pas de mal non plus, mais n'a pas beaucoup d'impact pratique.

0 votes

KeyDerivationPrf, KeyDerivation et BlockCopy sont indéfinis, quelles sont leurs classes ?

27voto

Seb Nilsson Points 8619

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

D'un article sur Sitepoint :

Un pirate peut toujours effectuer ce que l'on appelle une attaque par dictionnaire. Les parties malveillantes peuvent faire une une attaque par dictionnaire en prenant, par par exemple, 100 000 mots de passe qu'ils qu'ils savent que les gens utilisent fréquemment (ex. noms de ville, équipes sportives, etc.), les hacher, et ensuite comparer chaque entrée du dictionnaire à chaque ligne de la table table de la base de données. Si les pirates trouvent une correspondance, bingo ! Ils ont votre mot de passe. Pour résoudre ce problème, cependant, nous il suffit de saler le hachage.

Pour saler un hash, on trouve simplement une chaîne de texte qui semble aléatoire, de la concaténer avec le mot de passe fourni par l'utilisateur, puis de hacher à la fois la chaîne générée aléatoirement et mot de passe générés de façon aléatoire en une seule valeur. Nous enregistrons ensuite le hachage et le sel en tant que champs séparés dans la table Users dans des champs distincts.

Dans ce scénario, non seulement un pirate devrait deviner le mot de passe, mais il doit aussi deviner le sel. L'ajout de sel au texte clair améliore la sécurité : maintenant, si un pirate tente une attaque par dictionnaire, il doit hacher ses 100 000 entrées avec le sel de chaque ligne ligne de l'utilisateur. Bien que cela soit toujours possible, les chances de piratage diminuent radicalement.

Il n'existe pas de méthode permettant de le faire automatiquement dans .NET, vous devrez donc vous contenter de la solution ci-dessus.

0 votes

Les sels sont utilisés pour se défendre contre des choses comme les tables arc-en-ciel. Pour se défendre contre les attaques par dictionnaire, un facteur de travail (également connu sous le nom d'étirement de clé) est nécessaire comme tout bon KDF : fr.wikipedia.org/wiki/Key_stretching

5voto

thashiznets Points 61

Bah, c'est mieux ! http://sourceforge.net/projects/pwdtknet/ et il est meilleur parce que ..... il exécute Étirements clés ET utilise HMACSHA512 :)

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X