Réponses:
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 data
un tableau d'octets, vous pouvez utiliser
var data = Encoding.ASCII.GetBytes(password);
et pour récupérer la chaîne de md5data
ousha1data
var hashedPassword = ASCIIEncoding.GetString(md5data);
md5
c'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.
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 / Rfc2898DeriveBytes
pour 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 100000
peut être réduite. Une valeur minimale doit être d'environ 10000
.
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 .
V1
et V2
la méthode de vérification dont vous avez besoin.
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);
}
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);
}
}
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);
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".
using
instruction ou de l'appelerClear()
lorsque vous avez terminé d'utiliser l'implémentation.