1

I need to generate a hash in PHP that matches the output from the HASHBYTES function in T-SQL.

Here is the code that was originally used to generate the hash in T-SQL:

 declare @input varchar(15) =  [this is the incoming value entered by the user]
 declare @salt binary(32) =  CRYPT_GEN_RANDOM(32)
 declare @saltchars nvarchar(32) = cast(@salt as nvarchar(32) )
 declare @inputchars nvarchar(50) = cast(@input as nvarchar(50) )
 declare @sha binary(32) = HASHBYTES('SHA2_256', @passwordchars + @saltchars)

For the input "pete1234matt", we get an output of:

 salt =  0x75D6215CA44339B645E86BE790FF562D3EE4BC51034FDC6A9697F767BB961B5B
 sha  =  0x979EEAC93B5F6624269634202D9F3B5B8008113B03A83D104784184E0F9361F8

Now here's the PHP code that I've written, I take the provided and input, then encode those as UTF-16LE as I expect the cast to nvarchar does. Is that correct?

$password = 'pete1234matt'; 
$salt = 0x75D6215CA44339B645E86BE790FF562D3EE4BC51034FDC6A9697F767BB961B5B;
$saltchars = mb_convert_encoding($salt, 'UTF-16LE');
$passwordchars = mb_convert_encoding($password, 'UTF-16LE');
$sha = hash('sha256', $passwordchars . $saltchars);
//$sha ==   17b87bd0491f32f1e474ad9293e2fe5db56ed0a5c9939435f0984fdc04806dc5
//Expected: 979EEAC93B5F6624269634202D9F3B5B8008113B03A83D104784184E0F9361F8

I'm leaning towards there being an encoding issue with the salt but can't quite figure out where i'm going wrong. Can anyone with more knowledge on character encoding & T-SQl lend me a hand here!!

Matt Evans
  • 13
  • 2
  • What evidence do you have that suggests you need to convert everything to UTF-16? Does converting the encoding even alter the bytes? Or does it just change how it's displayed? – miken32 Apr 29 '21 at 21:33
  • Thanks! I could very well be misunderstanding, but NVARCHAR stores the value as UTF-16LE. So therefore, the value of the \@saltchars & \@inputchars would be encoded as UTF-16LE. I have tried with & without encoding as UTF-16LE, but still wasn't able to get the hash to match. – Matt Evans Apr 29 '21 at 21:39
  • You might want to echo out some of your values to see how they look. I expect your password will be untouched. For your salt, might be easier to work with a hex string rather than an integer. e.g. `$saltchars = hex2bin("75D6215CA44339B645E86BE790FF562D3EE4BC51034FDC6A9697F767BB961B5B");` – miken32 Apr 29 '21 at 22:03
  • So the outputted value is the same, but the actual string changes. If you remove the encoding, the end salt changes. Here's the output for the salt before/after encoding, notice the string length changes. `string(66) "0x75D6215CA44339B645E86BE790FF562D3EE4BC51034FDC6A9697F767BB961B5B" string(132) "0x75D6215CA44339B645E86BE790FF562D3EE4BC51034FDC6A9697F767BB961B5B"` – Matt Evans Apr 29 '21 at 22:13
  • @miken32 I did just try the hex2bin, both in place of the encoded salt & in place of the raw salt. No luck. :( – Matt Evans Apr 29 '21 at 22:15

1 Answers1

1
  1. The hex notation you're using is for numbers only in PHP. You want to put that in as a string and call hex2bin().
  2. I don't know why you're converting the salt to UTF16, but don't do that.
$password = 'pete1234matt'; 
$salt = hex2bin('75D6215CA44339B645E86BE790FF562D3EE4BC51034FDC6A9697F767BB961B5B');
$saltchars = $salt;
$passwordchars = mb_convert_encoding($password, 'UTF-16LE');
$sha = hash('sha256', $passwordchars . $saltchars);
//Expected: 979EEAC93B5F6624269634202D9F3B5B8008113B03A83D104784184E0F9361F8

var_dump($sha);

Output:

string(64) "979eeac93b5f6624269634202d9f3b5b8008113b03a83d104784184e0f9361f8"

Don't make a habit of calling any charset conversion function without explicitly specifying the source encoding. PHP strings are essentially just byte arrays with no concept of encoding. PHP has config settings that control the assumed default charset, but calling mb_convert_encoding($salt, 'UTF-16LE'); with the properly-decoded hex would interpret the string according to the PHP default, likely UTF8 or ISO-8859-1, and turn it into mojibake. When, in reality, that string is already UTF16LE.

Eg:

$test_8   = 'hello ';
$test_16  = mb_convert_encoding($test_8, 'UTF-16LE', 'UTF-8');
$mojibake = mb_convert_encoding($test_16, 'UTF-16LE');

var_dump(
    bin2hex($test_8),
    bin2hex($test_16),
    bin2hex($mojibake)
);

Output:

string(20) "68656c6c6f20f09f8d86"
string(32) "680065006c006c006f0020003cd846df"
string(64) "68000000650000006c0000006c0000006f000000200000003c003f0046003f00"
Sammitch
  • 30,782
  • 7
  • 50
  • 77