【问题标题】:Is my MVC3 Password Handling Adequate我的 MVC3 密码处理是否足够
【发布时间】:2012-04-05 11:42:25
【问题描述】:

我已经阅读了大量关于如何存储和比较输入的密码的文章,有些是旧的,有些是新的,但都不同。因此,我采用了我认为最好的选择并实施了以下方法。

请注意,我创建了自己的身份验证提供程序,我没有使用或继承标准 MS MembershipProvider(太臃肿)。这是针对不需要“超级安全”的轻量级站点,但我想遵循最佳实践。我只是想验证一下我没有做任何明显错误或打开安全漏洞的事情。另外,我查看了每个用户的密码加盐,但对于我的需求来说,它似乎过于复杂。这是一个有效的假设吗?

首先,我将以下 appSettings 键放入我的 web.config 中,该键由我的 configurationProvider 类返回。

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

有了这个,我有什么明显的遗漏吗?

【问题讨论】:

  • 如果您正在编写一个 Intranet 站点,为什么不使用 Windows 安全性?您滚动提供者的方法是一种很好的方法。 MS一号真的很臃肿。浏览您的代码或多或少是我为公共网站所做的,所以我认为您对所写的内容做得很好。请记住,一旦连接到网络,没有什么是真正安全的,您只是在设置层层障碍,希望攻击者感到无聊并离开。
  • 对不起,彼得...我应该澄清得更好。它是一个基于 Internet 的站点,但适用于一个少于 100 名成员将登录的慈善团体。我应该使用“门户”这个词。所以它不会有大量流量,但我正在无偿开发它来练习我的技能,所以我只想知道最佳实践。
  • 使用 PBKDF2 或 bcrypt 而不是自己滚动。

标签: c# asp.net-mvc encryption passwords


【解决方案1】:

我相信你的方案是足够的,但它可以在一点点改进。

在我看来,您已经开始使用您在 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 美分!

【讨论】:

    【解决方案2】:

    一些想法......

    1. 您应该使用 HMACSHA256 或 HMACSHA512 而不是 HMACSHA1。 .NET4 的默认哈希算法现在是 SHA256,而不是 SHA1。
    2. 当您获得加密密钥的字节时,您使用 ASCII 编码,但您使用 Unicode 编码计算哈希。保持一致 - 使用 Unicode。
    3. 考虑给哈希加盐。
    4. 您说的是加密,但实际上是在散列。

    【讨论】:

    • 谢谢大卫。我将根据第 1 点和第 2 点更改我的代码。对于第 3 点,我为低容量站点所做的是否足够,或者今天的标准是否真的需要加盐?我知道这是一个模棱两可的问题,但会喜欢你的意见。对于第 4 点,有什么区别?
    • 加密意味着有一个密钥可以让您加密和解密该值。散列涉及单向操作-实际上没有办法取消散列值(好吧,据我了解,这不是一种简单的方法)。我不是安全专家——这些都是我多年来不得不处理的事情。关于腌制,我一直认为这是一种很好的做法。我想这实际上取决于您对安全级别的舒适程度以及您存储的数据类型。如果是 PCI 数据,您可能希望将其锁定。如果是你的狗的照片,好吧,我想这取决于。 :)
    • 我很高兴能够提供帮助。祝你好运!
    猜你喜欢
    • 2013-05-23
    • 1970-01-01
    • 1970-01-01
    • 2019-04-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-09-11
    相关资源
    最近更新 更多