C#中的哈希和盐密码

我刚刚通过DavidHayden的哈希用户密码文章之一。

真的,我无法得到他想要达到的目标。

这是他的代码:

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

有没有其他的C#方法哈希密码和添加盐?

实际上,这有点奇怪,string转换 – 成员资格提供者将其放入configuration文件中。 哈希和盐是二进制的blob,你不需要将它们转换成string,除非你想把它们放到文本文件中。

在我的“ 开始ASP.NET安全”一书中,(最后是一本拉皮条的借口),我做了以下工作

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

盐的生成就是这个问题的例子。 您可以使用Encoding.UTF8.GetBytes(string)将文本转换为字节数组。 如果您必须将散列转换为其string表示forms,则可以使用Convert.ToBase64StringConvert.FromBase64String将其转换回来。

你应该注意到,你不能在字节数组上使用相等运算符,它会检查引用,所以你应该简单地循环两个数组,检查每个字节

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

始终使用每个密码的新盐。 盐不必保密,可以和散列一起存储。

什么blowdart说,但less一点代码。 使用Linq或CopyTo来连接数组。

 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有一个简单的方法来比较你的字节数组。

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

然而,在实施任何这个之前,看看这个post 。 对于密码哈希,你可能需要一个慢哈希algorithm,而不是一个快速的哈希algorithm。

为此目的, Rfc2898DeriveBytes类是慢的(并且可以变得更慢),并且可以回答原始问题的第二部分,因为它可以接收密码和盐并返回散列。 看到这个问题的更多信息。 请注意, Stack Exchange使用Rfc2898DeriveBytes进行密码散列( 此处为源代码)。

我一直在读SHA256这样的散列函数并不是真正用于存储密码的: https : //patrickmn.com/security/storing-passwords-securely/#notpasswordhashes

而是自适应密钥派生函数,如PBKDF2,bcrypt或scrypt。 这是一个基于PBKDF2的Microsoft在Microsoft.AspNet.Identity库中为PasswordHasher写的一个:

 /* ======================= * 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])); } 

注意这需要安装Microsoft.AspNet.Cryptography.KeyDerivation nuget包,它需要.NET 4.5.1或更高版本。 对于早期版本的.NET,请参阅Microsoft的System.Web.Helpers库中的Crypto类。

2015年11月更新
更新回答,以使用来自使用PBKDF2-HMAC-SHA256哈希而不是PBKDF2-HMAC-SHA1的不同Microsoft库的实现(注意,如果iterCount足够高,则PBKDF2-HMAC-SHA1 仍然是安全的 )。 您可以查看简化代码被复制的源代码,因为它实际上处理了从以前的答案中实现的validation和升级散列,如果将来需要增加iterCount,这将非常有用。

盐被用来为散列增加一个额外的复杂程度,使其更难以暴力破解。

从Sitepoint上的一篇文章 :

黑客仍然可以执行所谓的字典攻击。 恶意方面可能会通过例如他们知道人们经常使用的100,000个密码(例如城市名称,运动队等)进行字典攻击,将它们散列,然后将字典中的每个条目与数据库中的每一行进行比较表。 如果黑客find一个匹配,宾果! 他们有你的密码。 但是,为了解决这个问题,我们只需要encryption哈希。

为了提供一个散列,我们只是想出了一个随机的文本string,并将其与用户提供的密码连接起来,然后将随机生成的string和密码散列为一个值。 然后,我们将用户表中的散列和盐保存为单独的字段。

在这种情况下,黑客不仅需要猜测密码,还必须猜测盐。 在明文中添加盐可以提高安全性:现在,如果黑客尝试字典攻击,他必须用每个用户行的salt来散列他的100,000个条目。 尽pipe仍然有可能,黑客成功的可能性从根本上消失。

在.NET中没有自动执行此操作的方法,因此您将使用上面的解决scheme。

Bah,这个更好! http://sourceforge.net/projects/pwdtknet/它更好,因为;…..它执行关键拉伸和使用HMACSHA512 🙂

我已经制作了一个SimpleHashing.Net库来使用Microsoft提供的基本类来简化哈希过程。 普通的SHA不足以让安全地存储密码了。

该库使用Bcrypt的散列格式的思想,但由于没有官方的MS实现,所以我更喜欢使用框架中可用的内容(即PBKDF2),但是开箱即用太困难了。

这是一个如何使用库的简单例子:

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

这是我如何做到这一点..我创build哈希,并使用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); } } 

在回答这个原始问题的这个部分“是否有任何其他的C#哈希密码的方法”您可以使用ASP.NET身份v3.0 https://www.nuget.org/packages/Microsoft.AspNet.Identity。的EntityFramework / 3.0.0-RC1决赛

 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(string[] args) { WindowsIdentity wi = WindowsIdentity.GetCurrent(); var ph = new PasswordHasher<WindowsIdentity>(); Console.WriteLine(ph.HashPassword(wi,"test")); Console.WriteLine(ph.VerifyHashedPassword(wi,"AQAAAAEAACcQAAAAEA5S5X7dmbx/NzTk6ixCX+bi8zbKqBUjBhID3Dg1teh+TRZMkAy3CZC5yIfbLqwk2A==","test")); } } } 
 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)