Адекватна ли моя обработка паролей MVC3? - PullRequest
3 голосов
/ 21 марта 2012

Я прочитал тонну статей о том, как хранить и сравнивать введенные пароли, некоторые старые и некоторые новые, но все разные. Поэтому я взял то, что мне показалось лучшим вариантом и реализовал следующее.

Обратите внимание, что я создал свой собственный поставщик аутентификации, и я не использую и не наследую от стандартного MS MembershipProvider (слишком раздутый). Это для более легкого сайта, который не обязательно должен быть сверхобезопасным, но я хотел бы следовать рекомендациям. Я просто хотел бы получить подтверждение того, что я не делаю ничего плохого или открывающего дыры в безопасности. Кроме того, я посмотрел на соляцию паролей для каждого пользователя, но она казалась слишком сложной для моих нужд. Это правильное предположение?

Сначала я вставил следующий ключ appSettings в мой файл web.config, который возвращается моим configurationProvider class.

<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, просто базовая интрасеть)

С этим, есть что-то яркое, что я пропускаю?

Ответы [ 2 ]

2 голосов
/ 21 марта 2012

Я считаю, что ваша схема адекватна, но ее можно немного улучшить.

Мне кажется, что здесь у вас есть начало схемы засолки с вашим EncryptionKey, который есть в вашем файле app.config. Однако для обеспечения наилучшей безопасности обычно люди используют разные соли для каждого пароля и хранят соль вместе с хешем в базе данных.

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 цента!

1 голос
/ 21 марта 2012

Несколько мыслей ...

  1. Вы должны использовать HMACSHA256 или HMACSHA512 вместо HMACSHA1. Алгоритм хеширования .NET4 по умолчанию теперь SHA256 вместо SHA1.
  2. Когда вы получаете байты своего ключа шифрования, вы используете кодировку ASCII, но вы вычисляете хэш с использованием кодировки Unicode. Держите их согласованными - используйте Unicode.
  3. Подумайте о том, чтобы засолить хэш
  4. Вы говорите шифрование, но вы действительно хешируете.
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...