1

I am working on integrating a new Authentication rest API that our web dev team wrote. The last piece I need to get working will return a User's User_Id GUID when given a username and password hash that matches the SHA1 Base64Encoded password hash stored in our user database.

Using the following VB.Net code with a hardcoded username and password salt, I am able to create a valid password hash.

.....
.....
Dim passwordHash As String = EncodePassword("Testing1234", "CQ4znO+Dd28GRle54xSW6A==")

Public Function EncodePassword(ByVal pass As String, ByVal saltBase64 As String) As String
    Dim bytes As Byte() = Encoding.Unicode.GetBytes(pass)
    Dim src As Byte() = Convert.FromBase64String(saltBase64)
    Dim dst As Byte() = New Byte(src.Length + bytes.Length - 1) {}
    Buffer.BlockCopy(src, 0, dst, 0, src.Length)
    Buffer.BlockCopy(bytes, 0, dst, src.Length, bytes.Length)
    Dim algorithm As HashAlgorithm = HashAlgorithm.Create("SHA1")
    Dim inArray As Byte() = algorithm.ComputeHash(dst)
    Dim passwordHash AS String = Convert.ToBase64String(inArray)
    Return passwordHash
End Function

However, I cannot create the same password hash in SQL using the following code.

DECLARE @newPassword NVARCHAR(128) = 'Testing1234'
DECLARE @encoded_salt NVARCHAR(128) = 'CQ4znO+Dd28GRle54xSW6A=='
DECLARE @encoded_hashed_password NVARCHAR(128) = dbo.base64_encode(HASHBYTES('SHA1',
                                                     CONVERT(VARBINARY(MAX), @encoded_salt)
                                                     + CONVERT(VARBINARY(MAX), @newPassword)))
SELECT @encoded_hashed_password AS Password_Hash

The function code for dbo.base64_encode is here:

ALTER FUNCTION [dbo].[base64_encode] (@data VARBINARY(MAX))
RETURNS VARCHAR(MAX)
WITH SCHEMABINDING,
RETURNS NULL  ON NULL INPUT
BEGIN
    RETURN (SELECT [text()] = @data FOR XML PATH(''));
END;

Corresponding decode function (not used in example, but included for completeness)

ALTER FUNCTION [dbo].[base64_decode] (@base64_text VARCHAR(MAX))
RETURNS VARBINARY(MAX)
WITH SCHEMABINDING,
RETURNS NULL ON NULL INPUT
BEGIN
    DECLARE @x XML;
    SET @x = @base64_text; 
    RETURN @x.[value]('(/)[1]', 'VARBINARY(max)');
END;

Can anyone help me figure out why the SQL script that appears to me to be equivalent to the VB.Net code does not produce the same result?

Scope Creep
  • 547
  • 3
  • 15
  • 1
    The T-SQL code doesn't decode the Base64-encoded salt, it just converts the string representation. – Jeroen Mostert Dec 09 '19 at 18:17
  • Do you really need to pass the plaintext password to the database? The usual way is to store the salt and the hash in the database (and the number of rounds of PBKDF2 or whatever), and you would make a new salt for a new password too. Please have a read of [Salted Password Hashing - Doing it Right](https://crackstation.net/hashing-security.htm). – Andrew Morton Dec 09 '19 at 18:26
  • @JeroenMostert So first of all, thank you for looking into my problem. However, with all due respect, it appears to me to be encoding the output of HASHBYTES as Base64. I believe this to be true because if I select the output of HASHBYTES with the following line SELECT HASHBYTES('SHA1', CONVERT(VARBINARY(MAX), @encoded_salt) + CONVERT(VARBINARY(MAX), @newPassword)) I get what appears to be a hex string. Password_Hash ruct48bWppOTFyzx0+gYn8MQY60= vs HASH_Bytes_Output 0xAEE72DE3C6D6A69393172CF1D3E8189FC31063AD – Scope Creep Dec 09 '19 at 18:27
  • 1
    Oh, your result is Base64, that's not the problem -- but the line `Dim src As Byte() = Convert.FromBase64String(saltBase64)` in your original code is not translated properly in T-SQL as `CONVERT(VARBINARY(MAX), @encoded_salt)`. The original converts the Base64 string back to bytes, you convert the Base64 string to the Unicode representation of its characters. Not at all the same thing. Your original code will refuse `Hello, world!` for the salt, the T-SQL code will happily accept it. – Jeroen Mostert Dec 09 '19 at 18:31
  • @AndrewMorton All I have to go off of is the raw text of username and password entered in the login UI. If the application logic hits this code, it means the legacy auth service authenticated them. What I am trying to do is get a JWT token back from the new auth service, which requires a valid UserId GUID. Since this is a publicly exposed API, i am essentially doing a second Authentication pass, 1 with the old API and one with the new API so I can get the new APIs JWT token – Scope Creep Dec 09 '19 at 18:31
  • @JeroenMostert What you are saying makes sense. How can I modify the T-SQL code to replicate the VB.Net logic and produce the same output? If you can answer that question I will happily mark it as the accepted answer. Thanks again for your time – Scope Creep Dec 09 '19 at 18:35
  • 1
    You need the inverse `base64_decode` function; once you have that the rest falls into place. Here's [one question](https://stackoverflow.com/q/5082345/4137916) that addresses that. (Note that you only want to decode it to a `VARBINARY(MAX)`, you don't want to try making a string out of it.) – Jeroen Mostert Dec 09 '19 at 18:37
  • @JeroenMostert I'm not following your last comment. Neither the decode function I shared, nor the decode logic from the SO link you shared give me back my value. I'm not sure what I'm missing. In one of your previous answers you say: The original converts the Base64 string back to bytes, you convert the Base64 string to the Unicode representation of its characters. In order to reproduce the output of the VB.Net code in T-SQL, what would I need to change to convert the hardcoded password hash back into bytes? – Scope Creep Dec 09 '19 at 18:53
  • @JeroenMostert To be clear, what I am hoping is that you (or someone) can post a T-SQL script that turns a password "Testing1234" with a Salt value of "CQ4znO+Dd28GRle54xSW6A==" into a SHA1 hash value equal to "XV0nKadSNhFuduNcFihXSdeAbEw=" – Scope Creep Dec 09 '19 at 19:33
  • @JeroenMostert I got the code to work finally, and provided an update to the original post detailing the solution. I upvoted all your comments so you get some rep for your time. Thanks for you help! – Scope Creep Dec 09 '19 at 22:08
  • 2
    You're allowed to self-answer your question, and in fact I encourage you to do so rather than put the answer in the question. Questions without answers are more likely to get deleted. – Jeroen Mostert Dec 09 '19 at 22:20
  • @JeroenMostert I didn't know that was allowed. I went ahead and posted the answer, thanks! – Scope Creep Dec 10 '19 at 16:03

1 Answers1

0

I ended up getting it to work a couple hours after I posted the question, and figured I'd provide an update for any future readers that come across this post.

The encode/decode functions that I shared previously were working. The T-SQL script that I shared was also working, but I was using the wrong salt value. What I ended up figuring out, was that the salt value stored in our aspnet_membership table was not actually used to salt the password. In actuality, the value used as a salt was a UNIQUEIDENTIFIER generated by a call to NEWID(). The salt value I shared ("CQ4znO+Dd28GRle54xSW6A==") was actually the base64 representation of the UNIQUEIDENTIFIER, used to "salt" the password. Once I figured that out, I was able to use the [base64_decode] function I previously shared to decode the base64 string to its VARCHAR representation, then convert the VARCHAR value back to the original UNIQUEIDENTIFIER.

  --Shows the conversion from salt string back to original UNIQUEIDENTIFIER
  SELECT CONVERT(UNIQUEIDENTIFIER, [dbo].[base64_decode]  
  ('CQ4znO+Dd28GRle54xSW6A==');

Once I had the original UNIQUEIDENTIFIER, I used THAT value to salt that password using the TSQL script I shared earlier and presto the salt values matched.

 DECLARE @salt UNIQUEIDENTIFIER = NEWID()
 DECLARE @encoded_salt NVARCHAR(128) = dbo.base64_encode(@salt)
 DECLARE @encoded_hashed_password NVARCHAR(128) = dbo.base64_encode(HASHBYTES('SHA1',
                                                      CONVERT(VARBINARY(MAX), @salt)
                                                      + CONVERT(VARBINARY(MAX), 
                                                      @NewPassword)))
Scope Creep
  • 547
  • 3
  • 15