0

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?

6
  • 1
    (var)binary values can be concantated natively in T-SQL. SELECT 0xAB12 + 0x9541; is valid and results in 0xAB129541. The problem doesn't appear to be SQL Server here. Commented Sep 26 at 15:16
  • 2
    i'm not sure what you're doing but it should definitely not be byte, but byte[] unless you have a very short hash Commented Sep 26 at 15:23
  • Can you please add the full error text with stacktrace? Commented Sep 27 at 6:01
  • Was it too simple to store the salt and hashed password in a single, simple string? And did you know PasswordHasher Commented Sep 27 at 6:28
  • @SirRufo The table is a legacy one that i have no control over regarding the fields (although i agree with you). I've looked at 'PasswordHasher' and assumed you had to use the whole Identity framework to us it (?), I have tried it but didnt quite understand how to use the 'TUser' type with my custom user table and related User model, would this be possible? Commented Sep 28 at 15:55

1 Answer 1

1

Not a full answer but certainly a thinbg to fix. Assuming that _context.TestUsers is using the provided User type for DbSet then you need to fix datatypes (unless you have some magic conversions happening). According to the SqlDbType and SQL Server Data Type Mappings(a bit dated but highly doubt that this has changed significantly) docs varbinary should be mapped to Byte[] type, not Byte:

Name Description
... ...
VarBinary Array of type Byte. A variable-length stream of binary data ranging between 1 and 8,000 bytes. Implicit conversion fails if the byte array is greater than 8,000 bytes. Explicitly set the object when working with byte arrays larger than 8,000 bytes.
SQL Server Database Engine type .NET Framework type
... ...
varbinary Byte[]
Sign up to request clarification or add additional context in comments.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.