我刚刚浏览了DavidHayden关于Hashing User Passwords的一篇文章。
我真的无法得到他想要达到的目标。
这是他的代码:
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#方法用于散列密码并向其添加盐?
答案 0 :(得分:236)
实际上,这有点奇怪,使用字符串转换 - 成员资格提供程序将它们放入配置文件中。哈希和盐是二进制blob,除非要将它们放入文本文件中,否则不需要将它们转换为字符串。
在我的书中,Beginning ASP.NET Security,(最后,哦,借口皮条客这本书)我做了以下
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)
将文本转换为字节数组。如果必须将哈希值转换为其字符串表示形式,则可以使用Convert.ToBase64String
和Convert.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;
}
始终每个密码使用一个新的盐。盐不必保密,可以与散列本身一起存放。
答案 1 :(得分:43)
什么吹笛说,但代码少了一点。使用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);
}
在执行任何此操作之前,请查看this post。对于密码哈希,您可能需要慢速哈希算法,而不是快速哈希算法。
为此,Rfc2898DeriveBytes
类很慢(并且可以变得更慢),并且可以回答原始问题的第二部分,因为它可以获取密码和salt并返回哈希。有关详细信息,请参阅this question。请注意,Stack Exchange is using Rfc2898DeriveBytes
用于密码哈希(源代码here)。
答案 2 :(得分:29)
我一直在阅读像SHA256这样的散列函数并不真正用于存储密码: https://patrickmn.com/security/storing-passwords-securely/#notpasswordhashes
而是自适应密钥派生函数,如PBKDF2,bcrypt或scrypt。以下是Microsoft在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]));
}
请注意,这需要安装Microsoft.AspNetCore.Cryptography.KeyDerivation nuget软件包,该软件包需要.NET Standard 2.0(.NET 4.6.1或更高版本)。对于早期版本的.NET,请参阅Microsoft的System.Web.Helpers库中的Crypto类。
2015年11月更新
更新了使用来自不同Microsoft库的实现的答案,该库使用PBKDF2-HMAC-SHA256散列而不是PBKDF2-HMAC-SHA1(如果iterCount足够高,请注意PBKDF2-HMAC-SHA1为still secure)。您可以查看source复制的简化代码,因为它实际上处理了从上一个答案实现的验证和升级哈希,如果您将来需要增加iterCount,则非常有用。
答案 3 :(得分:24)
Salt用于为哈希添加额外的复杂性,使其更难以暴力破解。
黑客仍然可以执行 什么叫字典攻击。 恶意当事方可以提出 字典攻击通过采取,为 例如,他们有100,000个密码 知道人们经常使用(例如城市 名字,运动队等),哈希, 然后比较中的每个条目 反对每一行的字典 数据库表。如果黑客找到了 比赛,宾果游戏!他们有你的密码。 然而,要解决这个问题,我们 只需要哈希哈希。
要为哈希加盐,我们只想出来 一个随机的文字串, 将其与密码连接起来 由用户提供,然后哈希 随机生成的字符串和 密码一起作为一个值。我们 然后保存哈希和盐 作为用户中的单独字段 表
在这种情况下,不仅是一个 黑客需要猜密码, 他们也必须猜测盐。 向明文添加盐可以改善 安全性:现在,如果黑客尝试了 字典攻击,他必须哈希他的 每个人的盐有100,000个条目 用户行。虽然它还在 可能,黑客的机会 成功从根本上消失。
在.NET中没有自动执行此操作的方法,因此您将使用上述解决方案。
答案 4 :(得分:6)
我创建了一个具有以下方法的类:
验证输入
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);
}
}
`
答案 5 :(得分:5)
呸,这更好! http://sourceforge.net/projects/pwdtknet/并且它更好,因为它执行键拉伸并使用 HMACSHA512 :)
答案 6 :(得分:3)
我已经建立了一个库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);
答案 7 :(得分:2)
我是这样做的..我创建哈希并使用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);
}
}
答案 8 :(得分:1)
我读了所有的答案,我认为那些足够的,特别是 @Michael 文章的缓慢哈希和 @CodesInChaos 好的评论,但我决定分享我的代码片段进行哈希/验证可能有用,并且不需要[ 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;
}
}
请注意SlowEquals功能是如此重要,最后,我希望这有帮助,请不要犹豫,告诉我更好的方法。
答案 9 :(得分:1)
使用Microsoft的System.Web.Helpers.Crypto
NuGet程序包。 它会自动向哈希添加盐。
您对这样的密码进行哈希处理:var hash = Crypto.HashPassword("foo");
您可以像这样验证密码:var verified = Crypto.VerifyHashedPassword(hash, "foo");
答案 10 :(得分:1)
如果您不使用asp.net或.net核心,那么> = .Net Standard 2.0项目中也有一种简便的方法。
首先,您可以设置所需的散列,盐和迭代数的大小,该大小与散列生成的持续时间有关:
private const int SaltSize = 32;
private const int HashSize = 32;
private const int IterationCount = 10000;
要产生密码哈希和盐,您可以使用如下代码:
public static string GeneratePasswordHash(string password, out string salt)
{
using (Rfc2898DeriveBytes rfc2898DeriveBytes = new Rfc2898DeriveBytes(password, SaltSize))
{
rfc2898DeriveBytes.IterationCount = IterationCount;
byte[] hashData = rfc2898DeriveBytes.GetBytes(HashSize);
byte[] saltData = rfc2898DeriveBytes.Salt;
salt = Convert.ToBase64String(saltData);
return Convert.ToBase64String(hashData);
}
}
要验证用户输入的密码是否有效,可以检查数据库中的值:
public static bool VerifyPassword(string password, string passwordHash, string salt)
{
using (Rfc2898DeriveBytes rfc2898DeriveBytes = new Rfc2898DeriveBytes(password, SaltSize))
{
rfc2898DeriveBytes.IterationCount = IterationCount;
rfc2898DeriveBytes.Salt = Convert.FromBase64String(salt);
byte[] hashData = rfc2898DeriveBytes.GetBytes(HashSize);
return Convert.ToBase64String(hashData) == passwordHash;
}
}
以下单元测试显示了用法:
string password = "MySecret";
string passwordHash = PasswordHasher.GeneratePasswordHash(password, out string salt);
Assert.True(PasswordHasher.VerifyPassword(password, passwordHash, salt));
Assert.False(PasswordHasher.VerifyPassword(password.ToUpper(), passwordHash, salt));
答案 11 :(得分:0)
ip r show | awk '{print $3 $1}'
答案 12 :(得分:-1)
回答原问题的这一部分&#34;是否有任何其他C#方法用于散列密码&#34;您可以使用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(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"));
}
}
}
答案 13 :(得分:-4)
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)