Mots de passe de hachage et de sel en C #

Je passais en revue l’un des articles de DavidHayden sur Hashing User Passwords .

Je ne peux vraiment pas obtenir ce qu’il essaie de réaliser.

Voici son code:

private static ssortingng CreateSalt(int size) { //Generate a cryptographic random number. RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider(); byte[] buff = new byte[size]; rng.GetBytes(buff); // Return a Base64 ssortingng representation of the random number. return Convert.ToBase64Ssortingng(buff); } private static ssortingng CreatePasswordHash(ssortingng pwd, ssortingng salt) { ssortingng saltAndPwd = Ssortingng.Concat(pwd, salt); ssortingng hashedPwd = FormsAuthentication.HashPasswordForStoringInConfigFile( saltAndPwd, "sha1"); return hashedPwd; } 

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

En fait, c’est un peu étrange, avec les conversions de chaînes – que le fournisseur d’appartenance fait pour les placer dans des fichiers de configuration. Les hachages et les sels sont des blobs binarys, vous n’avez pas besoin de les convertir en chaînes, sauf si vous voulez les placer dans des fichiers texte.

Dans mon livre, Commencer la sécurité ASP.NET , (enfin, une excuse pour maquiller 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 l'exemple de la question. Vous pouvez convertir du texte en tableaux d'octets en utilisant Encoding.UTF8.GetBytes(ssortingng) . Si vous devez convertir un hachage en sa représentation sous forme de chaîne, vous pouvez utiliser Convert.ToBase64Ssortingng et Convert.FromBase64Ssortingng 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 simplement parcourir les deux tableaux en vérifiant chaque octet.

 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 ne doivent pas être tenus secrets et peuvent être stockés avec le hash même.

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

 public bool ConfirmPassword(ssortingng password) { byte[] passwordHash = Hash(password, _passwordSalt); return _passwordHash.SequenceEqual(passwordHash); } 

Avant de mettre en œuvre tout cela, consultez ce post . Pour le hachage de mot de passe, vous pouvez souhaiter 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 renvoyer un hachage. Voir cette question pour plus d’informations. Notez que Stack Exchange utilise Rfc2898DeriveBytes pour le hachage du mot de passe (code source ici ).

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

Au lieu de cela, les fonctions de dérivation de clé adaptatives telles que PBKDF2, bcrypt ou scrypt étaient. Voici un fichier basé 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 ssortingng HashPassword(ssortingng 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.ToBase64Ssortingng(outputBytes); } public bool VerifyHashedPassword(ssortingng hashedPassword, ssortingng providedPassword) { var decodedHashedPassword = Convert.FromBase64Ssortingng(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 le package nuget Microsoft.AspNet.Cryptography.KeyDerivation installé qui nécessite .NET 4.5.1 ou supérieur. Pour les versions antérieures de .NET, consultez la classe 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 autre bibliothèque Microsoft qui utilise le hachage PBKDF2-HMAC-SHA256 au lieu de PBKDF2-HMAC-SHA1 (notez que PBKDF2-HMAC-SHA1 est toujours sécurisé si iterCount est suffisamment élevé). Vous pouvez extraire la source à partir de laquelle le code simplifié a été copié car il gère en réalité la validation et la mise à niveau des hachages implémentés à partir de la réponse précédente, utile si vous devez augmenter iterCount à l'avenir.

Le sel est utilisé pour append un niveau de complexité supplémentaire au hachage, le rendant plus difficile à casser par force brute.

A partir d’un article sur Sitepoint :

Un pirate peut toujours effectuer ce qu’on appelle une attaque par dictionnaire. Les parties malveillantes peuvent lancer une attaque par dictionnaire en prenant, par exemple, 100 000 mots de passe que les utilisateurs utilisent fréquemment (noms de villes, équipes sportives, etc.), les hacher puis comparer chaque entrée du dictionnaire à chaque ligne de la firebase database. table. Si les pirates trouvent une correspondance, bingo! Ils ont votre mot de passe. Pour résoudre ce problème, cependant, il suffit de saler le hash.

Pour saler un hachage, nous trouvons simplement une chaîne de texte aléatoire, la concaténons avec le mot de passe fourni par l’utilisateur, puis hachurons la chaîne et le mot de passe générés aléatoirement en une seule valeur. Nous enregistrons ensuite le hachage et le sel en tant que champs séparés dans la table Users.

Dans ce scénario, non seulement un pirate informatique devrait deviner le mot de passe, mais il devrait également deviner le sel. L’ajout de sel au texte en 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 utilisateur. Bien que cela soit encore possible, les chances de succès du piratage diminuent radicalement.

Il n’y a pas de méthode qui le fait automatiquement dans .NET, vous aurez donc la solution ci-dessus.

Bah, c’est mieux! http://sourceforge.net/projects/pwdtknet/ et c’est mieux parce que ….. il effectue l’ étirement des clés ET utilise HMACSHA512 🙂

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

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

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 ssortingng hashedPassword = simpleHash.Compute("Password123"); // Validating user's password by first loading it from database by username ssortingng storedHash = _repository.GetUserPasswordHash(username); isPasswordValid = simpleHash.Verify("Password123", storedHash); 

C’est comme ça que je le fais .. Je crée le hachage et le stocke en utilisant l’API ProtectedData :

  public static ssortingng GenerateKeyHash(ssortingng Password) { if (ssortingng.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); } } 

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

  1. Créer du sel
  2. Entrée de hachage
  3. Valider l’entrée

     public class CryptographyProcessor { public ssortingng CreateSalt(int size) { //Generate a cryptographic random number. RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider(); byte[] buff = new byte[size]; rng.GetBytes(buff); return Convert.ToBase64Ssortingng(buff); } public ssortingng GenerateHash(ssortingng input, ssortingng salt) { byte[] bytes = Encoding.UTF8.GetBytes(input + salt); SHA256Managed sHA256ManagedSsortingng = new SHA256Managed(); byte[] hash = sHA256ManagedSsortingng.ComputeHash(bytes); return Convert.ToBase64Ssortingng(hash); } public bool AreEqual(ssortingng plainTextInput, ssortingng hashedInput, ssortingng salt) { ssortingng newHashedPin = GenerateHash(plainTextInput, salt); return newHashedPin.Equals(hashedInput); } } 

    `

J’ai lu toutes les réponses et je pense que celles-ci suffisent, spécialement les articles @Michael avec un hachage lent et les bons commentaires @CodesInChaos , mais j’ai décidé de partager mon extrait de code pour le hachage / validation utile et inutile [ 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; } } 

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

  protected void m_GenerateSHA256_Button1_Click(objectSender, EventArgs e) { ssortingng salt =createSalt(10); ssortingng hashedPassword=GenerateSHA256Hash(m_UserInput_TextBox.Text,Salt); m_SaltHash_TextBox.Text=Salt; m_SaltSHA256Hash_TextBox.Text=hashedPassword; } public ssortingng createSalt(int size) { var rng= new System.Security.Cyptography.RNGCyptoServiceProvider(); var buff= new byte[size]; rng.GetBytes(buff); return Convert.ToBase64Ssortingng(buff); } public ssortingng GenerateSHA256Hash(ssortingng input,ssortingng salt) { byte[]bytes=System.Text.Encoding.UTF8.GetBytes(input+salt); new System.Security.Cyptography.SHA256Managed(); byte[]hash=sha256hashSsortingng.ComputedHash(bytes); return bytesArrayToHexSsortingng(hash); } 

En réponse à cette partie de la question initiale “Existe-t-il une autre méthode C # pour le hachage des mots de passe”? Vous pouvez y parvenir en utilisant ASP.NET Identity v3.0 https://www.nuget.org/packages/Microsoft.AspNet.Identity. EntityFramework / 3.0.0-rc1-final

 using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; using Microsoft.AspNet.Identity; using System.Security.Principal; namespace HashTest{ class Program { static void Main(ssortingng[] args) { WindowsIdentity wi = WindowsIdentity.GetCurrent(); var ph = new PasswordHasher(); Console.WriteLine(ph.HashPassword(wi,"test")); Console.WriteLine(ph.VerifyHashedPassword(wi,"AQAAAAEAACcQAAAAEA5S5X7dmbx/NzTk6ixCX+bi8zbKqBUjBhID3Dg1teh+TRZMkAy3CZC5yIfbLqwk2A==","test")); } } } 

Seulement un petit complément aux autres réponses, concernant la façon de choisir quel algorithme utiliser parmi ceux fournis par le .Net Framework dans l’ espace de noms Cryptography .

Selon l’article de Cisco sur les algorithmes de chiffrement de nouvelle génération :

  • MD5 doit être évité
  • SHA-1 est un inheritance
  • SHA-256, -384 et -512 font partie des algorithmes de chiffrement de nouvelle génération

En outre, parmi les différentes implémentations possibles:

  • toutes les implémentations postfixées – Cng et – CryptoServiceProvider sont certifiées FIPS – par conséquent recommandé
  • les algorithmes de hachage -Cng utilisent bcrypt
  • les versions -Managed sont également supscopes par les anciennes versions de .NET Framework
    (par exemple, SHA512, Managed commence à partir de 1.1 et Cng à partir de 3.5)
  • les versions gérées sont compatibles avec les anciennes versions du système d’exploitation

Donc, si vous n’êtes pas contraint par une ancienne version de .Net Framework ou par la version du système d’exploitation à prendre en charge, un bon choix serait peut-être SHA512Cng .

Vous pouvez également utiliser cette implémentation : bien que SHA512Cng ne soit pas utilisé, l’algorithme peut être personnalisé.

 create proc [dbo].[hash_pass] @family nvarchar(50), @username nvarchar(50), @pass nvarchar(Max),``` @semat nvarchar(50), @tell nvarchar(50) as insert into tbl_karbar values (@family,@username,(select HASHBYTES('SHA1' ,@pass)),@semat,@tell)