Comment hacher un mot de passe


117

J'aimerais stocker le hachage d'un mot de passe sur le téléphone, mais je ne sais pas comment le faire. Je n'arrive qu'à trouver des méthodes de cryptage. Comment le mot de passe doit-il être haché correctement?

Réponses:


62

MISE À JOUR : CETTE RÉPONSE EST GRAVEMENT DÉCOULÉE . Veuillez plutôt utiliser les recommandations de https://stackoverflow.com/a/10402129/251311 .

Vous pouvez soit utiliser

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

ou

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

Pour obtenir dataun tableau d'octets, vous pouvez utiliser

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

et pour récupérer la chaîne de md5dataousha1data

var hashedPassword = ASCIIEncoding.GetString(md5data);

11
Je recommanderais VRAIMENT d'utiliser SHA1. MD5 est un non-non, sauf si vous maintenez une compatibilité descendante avec un système existant. De plus, assurez-vous de le mettre dans une usinginstruction ou de l'appeler Clear()lorsque vous avez terminé d'utiliser l'implémentation.
vcsjones

3
@vcsjones: Je ne veux pas faire la guerre sainte ici, mais md5c'est assez bon pour presque tous les types de tâches. Ses vulnérabilités font également référence à des situations très spécifiques et obligent presque l'attaquant à en savoir beaucoup sur la cryptographie.
zerkms

4
@zerkms point pris, mais s'il n'y a aucune raison de compatibilité ascendante, il n'y a aucune raison d'utiliser MD5. "Mieux vaut prévenir que guérir".
vcsjones

4
Aucune raison d'utiliser MD5 à ce stade. Etant donné que le temps de calcul est insignifiant, il n'y a aucune raison d'utiliser MD5 sauf comme compatibilité avec les systèmes existants. Même si MD5 est "assez bon", il n'y a aucun coût avec l'utilisateur le SHA beaucoup plus sécurisé. Je suis sûr que les zerkms le savent, le commentaire s'adresse davantage à celui qui pose la question.
Gerald Davis

11
Trois grosses erreurs: 1) ASCII dégrade silencieusement les mots de passe avec des caractères inhabituels 2) Le simple MD5 / SHA-1 / SHA-2 est rapide. 3) Vous avez besoin d'un sel. | Utilisez plutôt PBKDF2, bcrypt ou scrypt. PBKDF2 est le plus simple dans la classe Rfc2898DeriveBytes (pas sûr s'il est présent sur WP7)
CodesInChaos

298

La plupart des autres réponses ici sont quelque peu dépassées par rapport aux meilleures pratiques d'aujourd'hui. En tant que tel, voici l'application de l'utilisation de PBKDF2 / Rfc2898DeriveBytespour stocker et vérifier les mots de passe. Le code suivant est dans une classe autonome dans cet article: Un autre exemple de la façon de stocker un hachage de mot de passe salé . Les bases sont vraiment simples, alors voici les détails:

ÉTAPE 1 Créez la valeur salt avec un PRNG cryptographique:

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

ÉTAPE 2 Créez le Rfc2898DeriveBytes et obtenez la valeur de hachage:

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

ÉTAPE 3 Combinez les octets de sel et de mot de passe pour une utilisation ultérieure:

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

ÉTAPE 4 Transformez le sel et le hachage combinés en une chaîne pour le stockage

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

ÉTAPE 5 Vérifiez le mot de passe saisi par l'utilisateur par rapport à un mot de passe enregistré

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

Remarque: selon les exigences de performance de votre application spécifique, la valeur 100000peut être réduite. Une valeur minimale doit être d'environ 10000.


8
@Daniel essentiellement, le message consiste à utiliser quelque chose de plus sûr qu'un hachage seul. Si vous hachez simplement un mot de passe, même avec du sel, les mots de passe de vos utilisateurs seront compromis (et probablement vendus / publiés) avant même que vous ayez une chance de leur dire de le changer. Utilisez le code ci-dessus pour rendre la tâche difficile pour l'attaquant, pas facile pour le développeur.
csharptest.net

2
@DatVM Non, un nouveau sel pour chaque fois que vous stockez un hachage. c'est pourquoi il est combiné avec le hachage pour le stockage afin que vous puissiez vérifier un mot de passe.
csharptest.net

9
@CiprianJijie, tout l'intérêt est que vous n'êtes pas censé pouvoir le faire.
csharptest.net

9
Si quelqu'un utilise une méthode VerifyPassword, si vous souhaitez utiliser Linq et un appel plus court pour un booléen, cela ferait: return hash.SequenceEqual (hashBytes.Skip (_saltSize));
Jesú Castillo

2
@ csharptest.net Quels types de tailles de tableaux recommandez-vous? la taille du tableau affecte-t-elle beaucoup la sécurité de toute façon? Je ne sais pas grand-chose sur le hachage / la cryptographie
lennyy

71

Sur la base de la bonne réponse de csharptest.net , j'ai écrit une classe pour ceci:

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

Usage:

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

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

Un exemple de hachage pourrait être ceci:

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

Comme vous pouvez le voir, j'ai également inclus les itérations dans le hachage pour une utilisation facile et la possibilité de mettre à niveau cela, si nous avons besoin de mettre à niveau.


Si vous êtes intéressé par .net core, j'ai également une version .net core sur Code Review .


1
Juste pour vérifier, si vous mettez à niveau le moteur de hachage, vous incrémenteriez la section V1 de votre hachage et la clé hors de cela?
Mike Cole

1
Oui, c'est le plan. Vous pouvez ensuite décider sur la base V1et V2la méthode de vérification dont vous avez besoin.
Christian Gollhardt

Merci pour la réponse et la classe. Je le mets en œuvre au moment où nous parlons.
Mike Cole

2
Oui @NelsonSilva. C'est à cause du sel .
Christian Gollhardt

1
Avec tout le copier / coller de ce code (y compris moi), j'espère que quelqu'un prendra la parole et que le message sera révisé si un problème est trouvé avec lui! :)
pettys

14

J'utilise un hachage et un sel pour le cryptage de mon mot de passe (c'est le même hachage utilisé par 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 pour utiliser SHA-1 ordinaire, qui est rapide. Utilisez une fonction de dérivation de clé lente, telle que PBKDF2, bcrypt ou scrypt.
CodesInChaos

1
  1. Créer un sel,
  2. Créez un mot de passe de hachage avec salt
  3. Économisez du hasch et du sel
  4. décrypter avec mot de passe et sel ... donc les développeurs ne peuvent pas décrypter le mot de passe
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

Les réponses de @ csharptest.net et de Christian Gollhardt sont excellentes, merci beaucoup. Mais après avoir exécuté ce code en production avec des millions d'enregistrements, j'ai découvert qu'il y avait une fuite de mémoire. Les classes RNGCryptoServiceProvider et Rfc2898DeriveBytes sont dérivées de IDisposable mais nous ne les supprimons pas. J'écrirai ma solution comme réponse si quelqu'un a besoin d'une version supprimée.

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

    }
}

Usage:

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

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

0

Je pense que l'utilisation de KeyDerivation.Pbkdf2 est meilleure que Rfc2898DeriveBytes.

Exemple et explication: mots de passe de hachage dans 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=
 */

Ceci est un exemple de code de l'article. Et c'est un niveau de sécurité minimum. Pour l'augmenter, j'utiliserais à la place du paramètre KeyDerivationPrf.HMACSHA1

KeyDerivationPrf.HMACSHA256 ou KeyDerivationPrf.HMACSHA512.

Ne faites pas de compromis sur le hachage de mot de passe. Il existe de nombreuses méthodes mathématiquement valables pour optimiser le hachage de mot de passe. Les conséquences pourraient être désastreuses. Une fois qu'un malfaiteur peut mettre la main sur la table de hachage des mots de passe de vos utilisateurs, il lui sera relativement facile de casser les mots de passe étant donné que l'algorithme est faible ou que la mise en œuvre est incorrecte. Il a beaucoup de temps (temps x puissance de l'ordinateur) pour déchiffrer les mots de passe. Le hachage du mot de passe doit être cryptographiquement puissant pour transformer «beaucoup de temps» en «temps déraisonnable ».

Encore un point à ajouter

La vérification du hachage prend du temps (et c'est bien). Lorsque l'utilisateur saisit un nom d'utilisateur incorrect, il ne faut pas de temps pour vérifier que le nom d'utilisateur est incorrect. Lorsque le nom d'utilisateur est correct, nous commençons la vérification du mot de passe - c'est un processus relativement long.

Pour un hacker, il serait très facile de comprendre si l'utilisateur existe ou non.

Assurez-vous de ne pas renvoyer de réponse immédiate lorsque le nom d'utilisateur est erroné.

Inutile de dire: ne répondez jamais à ce qui ne va pas. Juste général "Les informations d'identification sont fausses".


1
BTW, la réponse précédente stackoverflow.com/a/57508528/11603057 n'est pas correcte et nuisible. C'est un exemple de hachage, pas de hachage de mot de passe. Il doit s'agir d'itérations de la fonction pseudo-aléatoire pendant le processus de dérivation de clé. Il n'y a pas. Je ne peux pas le commenter ou voter contre (ma mauvaise réputation). Veuillez ne pas manquer les réponses incorrectes!
Albert Lyubarsky
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.