In my SQL Server legacy User table, I have the following columns (which I cannot change):
UserName: VARCHAR(50)
PasswodsHashed: VARBINARY
PasswordSalted: VARBIANRY
This table has thousands of records where the password was previously hashed using SHA512 via a stored procedure. I want to move the logic away from using a stored procedure into my ASP.NET Core 9 Web API.
Initially I am concentrating on the 'verify existing user/password' rather than the hash/create user.
I have the following models to handle the user details, login request & login response as follows:
public class User // (SQL Server user table representation)
{
public string Username { get; set; } = null!;
public byte PasswordHashed { get; set; } // Should this be a BinaryData type?
public byte SaltHashed { get; set; }
}
public class UserLoginRequest
{
public string Username { get; set; } = null!;
public string Password { get; set; } = null!;
}
public class UserLoginResponse
{
public User UserDetails { get; set; } = null!;
public string Token { get; set; } = null!;
}
I am using the following SecretHasher (.NET 6+ from 2022) class which I recently found here
public static class SecretHasher
{
private const int _saltSize = 16; // 128 bits
private const int _keySize = 32; // 256 bits
private const int _iterations = 50000;
private static readonly HashAlgorithmName _algorithm = HashAlgorithmName.SHA256;
private const char segmentDelimiter = ':';
public static string Hash(string input)
{
byte[] salt = RandomNumberGenerator.GetBytes(_saltSize);
byte[] hash = Rfc2898DeriveBytes.Pbkdf2(
input,
salt,
_iterations,
_algorithm,
_keySize
);
return string.Join(
segmentDelimiter,
Convert.ToHexString(hash),
Convert.ToHexString(salt),
_iterations,
_algorithm
);
}
public static bool Verify(string input, string hashString)
{
string[] segments = hashString.Split(segmentDelimiter);
byte[] hash = Convert.FromHexString(segments[0]);
byte[] salt = Convert.FromHexString(segments[1]);
int iterations = int.Parse(segments[2]);
HashAlgorithmName algorithm = new HashAlgorithmName(segments[3]);
byte[] inputHash = Rfc2898DeriveBytes.Pbkdf2(
input,
salt,
iterations,
algorithm,
hash.Length
);
return CryptographicOperations.FixedTimeEquals(inputHash, hash);
}
}
In my UserService, I have this issue in reading/converting the stored binary hashed pwd & salt columns, and matching them with the user's password input (string) to pass as a string to the Verify(...,..) method in the SecretHasher class shown above:
public async Task<UserLoginResponse> Login(UserLoginRequest logindetails)
{
// Fetch the stored hash password and hash salt
var user = _context.TestUsers.FirstOrDefault(u => u.Username.ToLower() == logindetails.Username.ToLower());
// I am having problems in the conversion on the next line from byte to string and I have tried several different ways using cast, ToString() etc etc..
// I also need to concatenate PasswordHashed & SaltHashed to pass to the Verify method as a single input(?) ***
var passwordHash = (byte)user.PasswordHashed; // + byte (byte)user.SaltHashed;
string hashed = SecretHasher.Hash(passwordSalt.ToString());
bool isPasswordCorrect = SecretHasher.Verify(logindetails.Password, passwordSalt.ToString());
Console.WriteLine("isPasswordCorrect= " + isPasswordCorrect);
// etc etc...
}
The usual error I get is
Cannot convert System.Byte to Byte (with the concatenation removed.
So the basic question is: how do I convert two binary columns into a one concatenated string?
(var)binaryvalues can be concantated natively in T-SQL.SELECT 0xAB12 + 0x9541;is valid and results in0xAB129541. The problem doesn't appear to be SQL Server here.byte, butbyte[]unless you have a very short hash