Я считаю, что ваша схема адекватна, но ее можно немного улучшить.
Мне кажется, что здесь у вас есть начало схемы засолки с вашим 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 цента!