91 lines
3.5 KiB
C#
91 lines
3.5 KiB
C#
using System;
|
|
using System.Data.SqlClient;
|
|
using Microsoft.AspNetCore.Cryptography.KeyDerivation;
|
|
|
|
namespace SecureCore.Authentication
|
|
{
|
|
public class PasswordManager
|
|
{
|
|
//Add some pepper to the passwords for good measure:
|
|
//https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html
|
|
private static string Pepper { get; } = "rVk/OwQUw01qy76Q+5WimPk+NdqUMMghftMXyJzzckOj/+eFn056PDYzBD61E/ZNjRdgiMK6RhcHEcdfpJdbcw==";
|
|
|
|
private static string ConnectionString = @"Server=DESKTOP-OEDDVKC\SQLEXPRESS;Database=main;Integrated Security=true;";
|
|
|
|
private static int Iterations { get; } = 100000;
|
|
private static KeyDerivationPrf KeyType { get; } = KeyDerivationPrf.HMACSHA512;
|
|
private static int KeySize { get; } = 512 / 8;
|
|
private static int SaltSize { get; } = 128 / 8; //128 bit salt
|
|
//As noted here: https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#maximum-password-lengths
|
|
//allowing passwords that are too long can result in a denial-of-service attack. So we must enforce password length limits.
|
|
//The recommended length is between 64 and 128, so I decided to go for the upper bounds.
|
|
public static int MaxPasswordLength { get; } = 128;
|
|
|
|
public PasswordManager()
|
|
{
|
|
}
|
|
|
|
public static (string Hash, string Salt) HashPassword(string password)
|
|
{
|
|
var salt = new byte[SaltSize];
|
|
|
|
ByteGenerator.GetRandomBytes(ref salt);
|
|
|
|
return (GetHash(password, salt), Convert.ToBase64String(salt));
|
|
}
|
|
|
|
public static (bool IsValid, string Message) PasswordIsValid(string password, string salt, string passwordHash)
|
|
{
|
|
if (password.Length > MaxPasswordLength) return (false, $"Password length exceeds {MaxPasswordLength} characters.");
|
|
|
|
var saltBytes = Convert.FromBase64String(salt);
|
|
|
|
if (passwordHash == GetHash(password, saltBytes))
|
|
return (true, string.Empty);
|
|
else
|
|
return (false, string.Empty);
|
|
}
|
|
|
|
public static (string PasswordHash, string SaltHash) GetUserPasswordHash(int userId)
|
|
{
|
|
using (var connection = new SqlConnection(ConnectionString))
|
|
{
|
|
using (var command = new SqlCommand("SELECT [Password Hash], [Salt Hash] FROM Login WHERE [User Key] = @UserId", connection))
|
|
{
|
|
command.Parameters.AddWithValue("UserId", userId);
|
|
|
|
connection.Open();
|
|
|
|
var reader = command.ExecuteReader();
|
|
|
|
if (!reader.HasRows) throw new MissingFieldException("No login records exist for this user.");
|
|
|
|
reader.Read();
|
|
|
|
return (reader["Password Hash"].ToString(), reader["Salt Hash"].ToString());
|
|
}
|
|
}
|
|
}
|
|
|
|
public static string HashStringData(string data, string salt = "")
|
|
{
|
|
var saltBytes = new byte[0];
|
|
|
|
if(!string.IsNullOrEmpty(salt))
|
|
saltBytes = Convert.FromBase64String(salt);
|
|
else
|
|
{
|
|
saltBytes = new byte[SaltSize];
|
|
ByteGenerator.GetRandomBytes(ref saltBytes);
|
|
}
|
|
|
|
return GetHash(data, saltBytes);
|
|
}
|
|
|
|
private static string GetHash(string password, byte[] salt)
|
|
{
|
|
return Convert.ToBase64String(KeyDerivation.Pbkdf2($"{password}{Pepper}", salt, KeyType, Iterations, KeySize));
|
|
}
|
|
}
|
|
}
|