Я бы выделил код для создания значений ha sh в его собственный метод, который вы можете отдельно протестировать.
Так вот:
private static bool VerifyPasswordHash(string password, byte[] storedHash, byte[] storedSalt)
{
if (password == null) throw new ArgumentNullException("password");
if (string.IsNullOrWhiteSpace(password)) throw new ArgumentException("Value cannot be empty or whitespace only string.", "password");
if (storedHash.Length != 64) throw new ArgumentException("Invalid length of password hash (64 bytes expected).", "passwordHash");
if (storedSalt.Length != 128) throw new ArgumentException("Invalid length of password salt (128 bytes expected).", "passwordHash");
using (var hmac = new System.Security.Cryptography.HMACSHA512(storedSalt))
{
var computedHash = hmac.ComputeHash(System.Text.Encoding.UTF8.GetBytes(password));
for (int i = 0; i < computedHash.Length; i++)
{
if (computedHash[i] != storedHash[i]) return false;
}
}
return true;
}
Получается так:
private static byte[] ComputeHash(string data, byte[] salt)
{
using (var hmac = new System.Security.Cryptography.HMACSHA512(salt))
{
return hmac.ComputeHash(System.Text.Encoding.UTF8.GetBytes(data));
}
}
private static bool VerifyPasswordHash(string password, byte[] storedHash, byte[] storedSalt)
{
if (password == null) throw new ArgumentNullException("password");
if (string.IsNullOrWhiteSpace(password)) throw new ArgumentException("Value cannot be empty or whitespace only string.", "password");
if (storedHash.Length != 64) throw new ArgumentException("Invalid length of password hash (64 bytes expected).", "passwordHash");
if (storedSalt.Length != 128) throw new ArgumentException("Invalid length of password salt (128 bytes expected).", "passwordHash");
var computedHash = ComputeHash(password, storedSalt);
for (int i = 0; i < computedHash.Length; i++)
{
if (computedHash[i] != storedHash[i]) return false;
}
return true;
}
Для этого есть несколько целей: он позволяет вам совместно использовать этот метод с кодом для генерации хэшей паролей при создании, изменении и сбросе, делая уверенным код, изначально равный ha sh пароли используют тот же процесс, что и код для проверки хэшей; позволяет выделить поколение ha sh для отдельного модульного теста; и это делает его немного более безопасным и простым в настройке алгоритма хеширования, если sha512 перестает быть жизнеспособным. Для этого есть и другие причины.
Пока я здесь, я мог бы также добавить поле authType
для пользователя, что облегчит и сделает более безопасным настройку этого алгоритма, если sha512 когда-либо перестанет быть жизнеспособным, и даже будет иметь два разных процесса активный в то же время. Например, вам может потребоваться отдельный процесс, если вам когда-либо понадобится интегрироваться с внешними службами OAuth или SAML.
Если у вас есть функция ComputeHash()
, вы должны сделать нечто подобное, чтобы создать функцию GenerateRandomSalt()
для вызывать при создании новых пользователей. С обоими из них создание справочных данных для вашего модульного теста полной аутентификации становится намного проще:
var fakeUser = new User()
{
Username = "anon1", FirstName = "fakename", LastName = "fakelastname",
Role = "admin", PasswordHash = null, PasswordSalt = GenerateRandomSalt()
};
fakeUser.PasswordHash = ComputeHash("somepassword", fakeUser.PasswordSalt);
using (var context = new DataContext(options))
{
context.Users.Add(fakeUser);
context.SaveChanges();
}