C# 中的哈希和盐密码
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/2138429/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Hash and salt passwords in C#
提问by ACP
I was just going through one of DavidHayden's articles on Hashing User Passwords.
我刚刚浏览了 DavidHayden 关于Hashing User Passwords的一篇文章。
Really I can't get what he is trying to achieve.
真的,我无法得到他想要达到的目标。
Here is his 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;
}
Is there any other C# method for hashing passwords and adding salt to it?
是否有任何其他 C# 方法可以对密码进行哈希处理并向其添加盐?
采纳答案by blowdart
Actually this is kind of strange, with the string conversions - which the membership provider does to put them into config files. Hashes and salts are binary blobs, you don't need to convert them to strings unless you want to put them into text files.
实际上这有点奇怪,字符串转换 - 成员资格提供者将它们放入配置文件。哈希和盐是二进制 blob,您不需要将它们转换为字符串,除非您想将它们放入文本文件中。
In my book, Beginning ASP.NET Security, (oh finally, an excuse to pimp the book) I do the following
在我的书《开始 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);
}
The salt generation is as the example in the question. You can convert text to byte arrays using Encoding.UTF8.GetBytes(string)
. If you must convert a hash to its string representation you can use Convert.ToBase64String
and Convert.FromBase64String
to convert it back.
盐的生成就是问题中的例子。您可以使用 将文本转换为字节数组Encoding.UTF8.GetBytes(string)
。如果您必须将散列转换为其字符串表示形式,则可以使用Convert.ToBase64String
并将Convert.FromBase64String
其转换回来。
You should note that you cannot use the equality operator on byte arrays, it checks references and so you should simply loop through both arrays checking each byte thus
您应该注意,您不能在字节数组上使用相等运算符,它会检查引用,因此您应该简单地遍历两个数组,从而检查每个字节
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;
}
Alwaysuse a new salt per password. Salts do not have to be kept secret and can be stored alongside the hash itself.
始终为每个密码使用新的盐。盐不必保密,可以与散列本身一起存储。
回答by Seb Nilsson
Salt is used to add an extra level of complexity to the hash, to make it harder to brute-force crack.
Salt 用于为散列增加额外的复杂性,使其更难暴力破解。
From an article on Sitepoint:
来自Sitepoint 上的一篇文章:
A hacker can still perform what's called a dictionary attack. Malicious parties may make a dictionary attack by taking, for instance, 100,000 passwords that they know people use frequently (e.g. city names, sports teams, etc.), hash them, and then compare each entry in the dictionary against each row in the database table. If the hackers find a match, bingo! They have your password. To solve this problem, however, we need only salt the hash.
To salt a hash, we simply come up with a random-looking string of text, concatenate it with the password supplied by the user, then hash both the randomly generated string and password together as one value. We then save both the hash and the salt as separate fields within the Users table.
In this scenario, not only would a hacker need to guess the password, they'd have to guess the salt as well. Adding salt to the clear text improves security: now, if a hacker tries a dictionary attack, he must hash his 100,000 entries with the salt of every user row. Although it's still possible, the chances of hacking success diminish radically.
黑客仍然可以执行所谓的字典攻击。恶意方可能会通过获取例如他们知道人们经常使用的 100,000 个密码(例如城市名称、运动队等),对它们进行散列,然后将字典中的每个条目与数据库中的每一行进行比较来进行字典攻击桌子。如果黑客找到匹配项,宾果游戏!他们有你的密码。然而,为了解决这个问题,我们只需要对哈希加盐。
为了给散列加盐,我们只是想出一个看起来随机的文本字符串,将它与用户提供的密码连接起来,然后将随机生成的字符串和密码作为一个值散列在一起。然后我们将哈希值和盐值保存为用户表中的单独字段。
在这种情况下,黑客不仅需要猜测密码,还必须猜测盐。在明文中添加盐可以提高安全性:现在,如果黑客尝试字典攻击,他必须用每个用户行的盐来散列他的 100,000 个条目。尽管仍有可能,但黑客成功的机会已经大大减少。
There is no method automatically doing this in .NET, so you'll have go with the solution above.
在 .NET 中没有自动执行此操作的方法,因此您将采用上述解决方案。
回答by Adam Boddington
What blowdart said, but with a little less code. Use Linq or CopyTo
to concatenate arrays.
什么blowdart说的,但代码少了一点。使用 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 has an easy way to compare your byte arrays too.
Linq 也有一个简单的方法来比较你的字节数组。
public bool ConfirmPassword(string password)
{
byte[] passwordHash = Hash(password, _passwordSalt);
return _passwordHash.SequenceEqual(passwordHash);
}
Before implementing any of this however, check out this post. For password hashing you may want a slow hash algorithm, not a fast one.
然而,在实施任何这些之前,请查看这篇文章。对于密码散列,您可能需要一种慢速散列算法,而不是快速散列算法。
To that end there is the Rfc2898DeriveBytes
class which is slow (and can be made slower), and may answer the second part of the original question in that it can take a password and salt and return a hash. See this questionfor more information. Note, Stack Exchange is using Rfc2898DeriveBytes
for password hashing (source code here).
为此,有Rfc2898DeriveBytes
一个很慢的类(并且可以变慢),并且可以回答原始问题的第二部分,因为它可以使用密码和盐并返回哈希值。有关更多信息,请参阅此问题。注意,Stack ExchangeRfc2898DeriveBytes
用于密码散列(源代码在这里)。
回答by Michael
I've been reading that hashing functions like SHA256 weren't really intended for use with storing passwords: https://patrickmn.com/security/storing-passwords-securely/#notpasswordhashes
我一直在读到像 SHA256 这样的散列函数并不是真的用于存储密码:https: //patrickmn.com/security/storing-passwords-securely/#notpasswordhashes
Instead adaptive key derivation functions like PBKDF2, bcrypt or scrypt were. Here is a PBKDF2 based one that Microsoft wrote for PasswordHasherin their Microsoft.AspNet.Identity library:
取而代之的是像 PBKDF2、bcrypt 或 scrypt 这样的自适应密钥派生函数。这是微软在其 Microsoft.AspNet.Identity 库中为PasswordHasher编写的基于 PBKDF2 的一个:
/* =======================
* 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]));
}
Note this requires Microsoft.AspNetCore.Cryptography.KeyDerivationnuget package installed which requires .NET Standard 2.0 (.NET 4.6.1 or higher). For earlier versions of .NET see the Cryptoclass from Microsoft's System.Web.Helpers library.
请注意,这需要安装需要 .NET Standard 2.0(.NET 4.6.1 或更高版本)的Microsoft.AspNetCore.Cryptography.KeyDerivationnuget 包。对于 .NET 的早期版本,请参阅Microsoft 的 System.Web.Helpers 库中的Crypto类。
Update Nov 2015
Updated answer to use an implementation from a different Microsoft library which uses PBKDF2-HMAC-SHA256 hashing instead of PBKDF2-HMAC-SHA1 (note PBKDF2-HMAC-SHA1 is still secureif iterCount is high enough). You can check out the sourcethe simplified code was copied from as it actually handles validating and upgrading hashes implemented from previous answer, useful if you need to increase iterCount in the future.
2015 年 11 月
更新更新答案以使用来自不同 Microsoft 库的实现,该库使用 PBKDF2-HMAC-SHA256 散列而不是 PBKDF2-HMAC-SHA1(注意,如果 iterCount 足够高,PBKDF2-HMAC-SHA1仍然是安全的)。您可以查看从简化代码复制的源代码,因为它实际上处理验证和升级从先前答案实现的哈希值,如果您将来需要增加 iterCount,则非常有用。
回答by thashiznets
Bah, this is better! http://sourceforge.net/projects/pwdtknet/and it is better because ..... it performs Key StretchingAND uses HMACSHA512:)
呵呵,这样更好!http://sourceforge.net/projects/pwdtknet/它更好,因为......它执行密钥拉伸并使用HMACSHA512:)
回答by JGU
This is how I do it.. I create the hash and store it using the ProtectedData
api:
我就是这样做的。我创建哈希并使用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);
}
}
回答by ehsan
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)
回答by Dave Cornall
In answer to this part of the original question "Is there any other C# method for hashing passwords" You can achieve this using ASP.NET Identity v3.0 https://www.nuget.org/packages/Microsoft.AspNet.Identity.EntityFramework/3.0.0-rc1-final
在回答原始问题“是否有任何其他 C# 方法用于散列密码”的这一部分时,您可以使用 ASP.NET Identity v3.0 https://www.nuget.org/packages/Microsoft.AspNet.Identity来实现这一点。实体框架/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(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"));
}
}
}
回答by Ilya Chernomordik
I have made a library SimpleHashing.Netto make the process of hashing easy with basic classes provided by Microsoft. Ordinary SHA is not really enough to have passwords stored securely anymore.
我已经创建了一个库SimpleHashing.Net以使用 Microsoft 提供的基本类使散列过程变得容易。普通的 SHA 已经不足以安全地存储密码了。
The library use the idea of hash format from Bcrypt, but since there is no official MS implementation I prefer to use what's available in the framework (i.e. PBKDF2), but it's a bit too hard out of the box.
该库使用来自 Bcrypt 的哈希格式的想法,但由于没有官方的 MS 实现,我更喜欢使用框架中的可用内容(即 PBKDF2),但开箱即用有点太难了。
This is a quick example how to use the library:
这是一个如何使用库的快速示例:
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);
回答by ankush shukla
protected void m_GenerateSHA256_Button1_Click(objectSender, EventArgs e)
{
string salt =createSalt(10);
string hashedPassword=GenerateSHA256Hash(m_UserInput_TextBox.Text,Salt);
m_SaltHash_TextBox.Text=Salt;
m_SaltSHA256Hash_TextBox.Text=hashedPassword;
}
public string createSalt(int size)
{
var rng= new System.Security.Cyptography.RNGCyptoServiceProvider();
var buff= new byte[size];
rng.GetBytes(buff);
return Convert.ToBase64String(buff);
}
public string GenerateSHA256Hash(string input,string salt)
{
byte[]bytes=System.Text.Encoding.UTF8.GetBytes(input+salt);
new System.Security.Cyptography.SHA256Managed();
byte[]hash=sha256hashString.ComputedHash(bytes);
return bytesArrayToHexString(hash);
}