我已经阅读了很多有关如何存储和比较输入的密码的文章,其中有些是旧的,有些是新的,但是都不同.因此,我选择了我认为是最好的选择,并实施了以下方法.
请注意,我创建了自己的身份验证提供程序,并且没有使用或继承标准MS MembershipProvider(太to肿).这是针对不需要“超级安全”的轻量级站点的,但是我想遵循最佳实践.我只想验证一下,我没有做任何明显的错误或打开安全漏洞.另外,我查看了每个用户的密码设置,但对于我的需求而言,它似乎过于复杂.这是一个有效的假设吗?
首先,我将以下appSettings密钥放入我的configurationProvider类返回的web.config中.
<add key="PasswordEncryptionKey" value="qTnY9lf...40 more random characters here" />
这是我的代码,可对用户进行公开身份验证,并根据输入的内容私密地检查存储的密码,并在保存时对密码进行编码.我没有显示添加或更新密码方法,因为它们使用相同的私有方法.
public bool Authenticate(string emailAddress, string password, bool setAuthCookie = false)
{
bool isAuthenticated = false;
var member = _memberRepository.Find(m => m.Email == emailAddress).SingleOrDefault();
if (member != null)
{
if (CheckPassword(password, member.Password))
{
isAuthenticated = true;
FormsAuthentication.SetAuthCookie(emailAddress, setAuthCookie);
}
}
return isAuthenticated;
}
private bool CheckPassword(string providedPassword, string storedPassword)
{
return EncodePassword(providedPassword) == storedPassword;
}
private string EncodePassword(string password)
{
var hash = new HMACSHA1
{
Key = Encoding.ASCII.GetBytes(_configurationProvider.PasswordEncryptionKey)
};
string encodedPassword = Convert.ToBase64String(hash.ComputeHash(Encoding.Unicode.GetBytes(password)));
return encodedPassword;
}
我唯一的规则是:
>密码必须在数据库中异步加密,并且不能解密.
>该方法应该是合理的,可以防止字典或其他攻击(我不是在开发FDIC受保站点,而只是在开发基本Intranet)
有了这个,有什么我想念的耀眼的东西吗?
解决方法:
我相信您的方案是足够的,但是可以稍作改进.
在我看来,您这里有一个盐腌方案的开始,并且您的app.config文件中具有EncryptionKey.但是,为了获得最佳安全性实践,通常人们为每个密码使用不同的盐,并将盐与哈希一起存储在数据库中.
class MyAuthClass {
private const int SaltSize = 40;
private ThreadLocal<HashAlgorithm> Hasher;
public MyAuthClass ()
{
// This is 'ThreadLocal' so your methods which use this are thread-safe.
Hasher = new ThreadLocal<HashAlgorithm>(
() => new HMACSHA256(Encoding.ASCII.GetBytes(_configurationProvider.PasswordEncryptionKey)
);
}
public User CreateUser(string email, string password) {
var rng = new RNGCryptoServiceProvider();
var pwBytes = Encoding.Unicode.GetBytes(password);
var salt = new byte[SaltSize];
rng.GetBytes(salt);
var hasher = Hasher.Value;
hasher.TransformBlock(salt, 0, SaltSize, salt, 0);
hasher.TransformFinalBlock(pwBytes, 0, pwBytes.Length);
var finalHash = hasher.Hash;
return new User { UserName = email, PasswordHash = finalHash, Salt = salt };
}
使用这样的方案,您的密码会变得更加复杂,因为如果攻击者碰巧得到了哈希,那么他还必须在蛮力攻击中猜出盐.
它与配置文件中的EncodingKey相同,但是更安全,因为每个哈希都有自己的盐.检查输入的密码类似:
public bool IsPasswordCorrect(User u, string attempt)
{
var hasher = Hasher.Value;
var pwBytes = Encoding.Unicode.GetBytes(attempt);
hasher.TransformBlock(u.Salt, 0, u.Salt.Length, Salt, 0);
hasher.TransformFinalBlock(pwBytes, 0, pwBytes.Length);
// LINQ method that checks element equality.
return hasher.Hash.SequenceEqual(u.PasswordHash);
}
} // end MyAuthClass
当然,如果您宁愿将哈希存储为字符串而不是字节数组,则欢迎这样做.
只是我的2美分!