Generate a PHP UTF-16 SHA1 hash to match C# method - c#

I'm trying to replicate some C# code in PHP5 and am having some difficulties.
The C# code is as following, and it is important to note that it cannot be changed:
string s = strToHash;
UnicodeEncoding encoding = new UnicodeEncoding();
byte[] bytes = encoding.GetBytes(s);
SHA1Managed managed = new SHA1Managed();
bytes = encoding.GetBytes(Convert.ToBase64String(managed.ComputeHash(bytes)) + "Space");
return Convert.ToBase64String(managed.ComputeHash(bytes));
The PHP code I've written to replicate this is as follows:
utfString = mb_convert_encoding($strToHash,"UTF-16");
hashTag = sha1($utfString,true);
base64Tag = base64_encode($hashTag);
encodedBase64Tag = mb_convert_encoding($base64Tag."Space","UTF-16");
base64EncodedAgain = base64_encode($encodedBase64Tag);
echo $base64EncodedAgain
However, the two outputs don't match up.
I believe this is because the SHA1 method in PHP works on ASCII encoded strings, not the encoding actually used by the passed in string.
I would like to get this to work, and I can't achieve it by altering the c# code (although no-doubt that would be the easiest fix).
Please can any advise on some ideas?
OK, I have altered the code following Artefacto's advice, and it still isn't working as expected.
The PHP Code now looks like this:
$utfString = "\xFF\xFE".mb_convert_encoding($strToHash,"UTF-16LE");
$hashTag = sha1($utfString,true);
$base64Tag = base64_encode($hashTag);
$encodedBase64Tag = "\xFF\xFE".mb_convert_encoding($base64Tag."Space","UTF-16LE");
$hashedAgain = sha1($encodedBase64Tag,true);
$base64EncodedAgain = base64_encode($hashedAgain);
echo $base64EncodedAgain."<Br/>";
And the outputed value of this method is:
1/Y5MCzI8vDJqc456YIicpwoyy0=
However, from the C# code, the value is this:
VPf7BhT1ksAfWbzeJw35g+bVKwY=

Well, try this code:
$utfString = mb_convert_encoding($strToHash,"UTF-16");
$hashTag = sha1($utfString,true);
$base64Tag = base64_encode($hashTag);
$encodedBase64Tag = mb_convert_encoding($base64Tag."Space","UTF-16");
$base64EncodedAgain = base64_encode(sha1($encodedBase64Tag, true));
echo $base64EncodedAgain
Because you miss one sha1 call.
Update
Now this code should work:
$utfString = mb_convert_encoding($strToHash,"UTF-16LE");
$hashTag = sha1($utfString,true);
$base64Tag = base64_encode($hashTag);
$encodedBase64Tag = mb_convert_encoding($base64Tag."Space","UTF-16LE");
$hashedAgain = sha1($encodedBase64Tag,true);
$base64EncodedAgain = base64_encode($hashedAgain);
echo $base64EncodedAgain . "<br />";

The docs for the no-arg constructor of UnicodeEncoding say this:
This constructor creates an instance that uses the little endian byte order, provides a Unicode byte order mark, and does not throw an exception when an invalid encoding is detected.
Now, mb_convert_encoding assumes "UTF-16" as "UTF-16BE" (big-endian). It also does not provide a BOM. Therefore, you must do instead:
$utfString = "\xFF\xFE" . mb_convert_encoding($strToHash,"UTF-16LE");
/* ...*/
$encodedBase64Tag = "\xFF\xFE" . mb_convert_encoding($base64Tag."Space","UTF-16LE");
As Paja pointed out, you're also missing a call to sha1.

This code should work...
$utfString = mb_convert_encoding($strToHash,"UTF-16LE");
$hashTag = sha1($utfString,true);
$base64Tag = base64_encode($hashTag);
echo $base64Tag;

Related

Getting issue when convert c# code to PHP as below

I am running this code:
var timeStamp = DateTime.UtcNow;
var sharedSecret = "xx";
var saltedString = timeStamp.ToString("2021-01-07T16:42:33.619667Z") + sharedSecret;
//Encoding saltedString using Unicode little-endian byte order
byte[] encodedSaltedString = Encoding.Unicode.GetBytes(saltedString);
//Hashing Algorithm used is SHA512
HashAlgorithm hash = new SHA512Managed();
//Compute Hash of encodedSaltedString
byte[] hashedEncodedString = hash.ComputeHash(encodedSaltedString);
//Convert hashed array to base64-encoded string
string signature = Convert.ToBase64String(hashedEncodedString);
I am then getting this result in C#:
"gQhjrLnY6fo44EeaaWaUBE1PY/8oEIRsUcK3AMSCVUCYMM4vRfxvQEEggXaHTF0GQbw4w2HbWArX1k6NnkzJFg=="
I converted to this code as below, but I am getting an issue. Can I get some help on this?
$timestamp = "2021-01-07T16:42:33.619667Z";
$sharedSecret = 'xx';
$saltedString = $timestamp.$sharedSecret;
$utf=mb_convert_encoding($saltedString, "UTF-16LE");
$signature = base64_encode(hash('sha512', $utf));
IN PHP I am getting this result:
ODEwODYzYWNiOWQ4ZTlmYTM4ZTA0NzlhNjk2Njk0MDQ0ZDRmNjNmZjI4MTA4NDZjNTFjMmI3MDBjNDgyNTU0MDk4MzBjZTJmNDVmYzZmNDA0MTIwODE3Njg3NGM1ZDA2NDFiYzM4YzM2MWRiNTgwYWQ3ZDY0ZThkOWU0Y2M5MTY=
But both should be same. The c# one is correct, I want the same in the php code as well.
From the PHP docs for hash:
hash ( string $algo , string $data , bool $binary = false ) : string|false
binary
   When set to true, outputs raw binary data. false outputs lowercase hexits.
You're not passing a value for $binary, so it's returning a string of hexadecimal characters.
The C# HashAlgorithm.ComputeHash method on the other hand binary data.
Since you're base64-encoding the result, you're presumably expecting the hash function to return binary data. You therefore need to pass true as the value for $binary:
$signature = base64_encode(hash('sha512', $utf, true));

How to get System.Text of C# in PHP?

Below is the code of both C# and PHP, I need some help regarding it. I am trying to generate the authenticationKey which is in C# but want the convert in PHP. All is done but I don't know how to implement [System.Text] in PHP as there is hash_hmac() in PHP but what might be the $string in the same function.
C# Version
var hmac = new System.Security.Cryptography.HMACSHA256();
var buffer = userName + accessKey + timeStamp + originUrl;
var hash = hmac.ComputeHash(System.Text.Encoding.UTF8.GetBytes(buffer));
var authenticationKey = Convert.ToBase64String(hash);
PHP version
$hmac = hash_hmac('sha256', $string, $buffer);
$encoded = base64_encode($hmac);
Can anyone help me with that. It will be very helpful. Thanks.
I was also searching for this and atlast it was just a mistake of 1 small varibale, in the hash_hmac function pass the last value as true, this is will return raw output and when you convert it to base64 it will give the same output as the c# code.
$buffer = $userName.$accessKey.$timeStamp.$originUrl;
$hmac = hash_hmac('sha256', $buffer, $tokenSecret, true);
$authenticationKey = base64_encode($hmac);
Just in your c# function use the key, as shown in the code below
var hmac = new System.Security.Cryptography.HMACSHA256();
hmac.Key = System.Text.Encoding.UTF8.GetBytes(tokenSecret);
In my code I have tokenSecret variable in bas64 form.
You may get the hex value, using unpack method:
$value = unpack('H*', buffer);
echo $value[1];
In your c# code, you used a randomly generated key. Check the documentation:
https://learn.microsoft.com/en-us/dotnet/api/system.security.cryptography.hmacsha256?view=netframework-4.8#constructors
HMACSHA256()
Initializes a new instance of the HMACSHA256 class with a randomly generated key.
HMACSHA256(Byte[])
Initializes a new instance of the HMACSHA256 class with the specified key data
Now the php documentation in comparison: https://www.php.net/manual/en/function.hash-hmac.php
hash_hmac ( string $algo , string $data , string $key [, bool $raw_output = FALSE ] ) : string
You see, what you call buffer in php, is actually the key and what you call buffer in c# is the data you hash.
So the$string should contain the data to be hashed.
As per the conversion, you don't need to get bytes in php: What is the PHP equivalent of this C# encoding code?

How to convert php's mb_convert_encoding() to C# equivalent

I have tried rather unsuccessfully , to convert the following php code to C#
and require help please.
php code is
$string="012014Te$ting#501834502014060007400";
$salt = "Cli3ntH#sah";
$utfString=mb_convert_encoding($string.$salt,ÄSCII");
$hashTag=sha1($utfString,true);
$Hash = base64_encode($hashTag);
with C# code
byte[] ascii = Encoding.ASCII.GetBytes(objtohash);
byte[] utf8 = Encoding.Convert(Encoding.ASCII, Encoding.UTF8, ascii);
byte[] hashBytes2 = sha1.ComputeHash(utf8);
var Hash = Convert.ToBase64String(hashBytes2);
also tried this, where objtohash = $string.$salt (i.e. concatenated)
var sha1 = new System.Security.Cryptography.SHA1Managed();
//convert to ascii byte array
byte[] AScii = EncodeAscii(objtohash);
//Hash it
byte[] hashBytes = sha1.ComputeHash(AScii);
//convert it to base 64
var Hash = Convert.ToBase64String(hashBytes);
I have tried several other ways as per SO, but I cannot get the same hashed value as the php sample.Hopefully someone can do it and hopefully give explanation as to why.
Thanks
The syntax error was finger trouble.
The answer it turns out, ..basically by trying any and all combinations of what i could find by googling is:
var objtohashArry = Encoding.ASCII.GetBytes(objtohash);
var HashSharresult = SHA1.Create().ComputeHash(objtohashArry);
var requestHash = Convert.ToBase64String(HashSharresult);
RequestHash = requestHash;

Trying to replicate C# hashing in Perl (on Linux)

I'm not coming up with the same values (using a known password).
I suspect it may be something having to do with encodings, but all the things I've tried haven't worked thus far:
windows code (c#?):
private static string EncodePassword(string password, string salt)
{
string encodedPassword = password;
HMACSHA1 hash = new HMACSHA1 { Key = Convert.FromBase64String(salt) };
encodedPassword = Convert.ToBase64String(hash.ComputeHash(Encoding.Unicode.GetBytes(password)));
return encodedPassword;
}
perl code run on linux:
use Modern::Perl '2015';
use Digest::SHA qw(hmac_sha1 hmac_sha1_base64);
use MIME::Base64 qw(decode_base64 encode_base64);
use Unicode::String qw(utf16be utf16le);
say encode_base64(hmac_sha1($password, decode_base64($salt)));
# (or, equivalently)
say hmac_sha1_base64($password, decode_base64($salt));
my $le16 = utf16le($password);
my $be16 = utf16be($password);
say "ok, try utf-16 (le, then be)...";
say encode_base64(hmac_sha1($le16, decode_base64($salt)));
say encode_base64(hmac_sha1($be16, decode_base64($salt)));
# try reversing the hmac output?
my $hmac_bytes = hmac_sha1($password, decode_base64($salt));
my $rev_bytes = reverse $hmac_bytes;
say encode_base64($rev_bytes);
In the original C# code, in this line:
encodedPassword = Convert.ToBase64String(hash.ComputeHash(Encoding.Unicode.GetBytes(password)));
a call to Encoding.Unicode.GetBytes transforms the password to a byte array via a UTF-16LE encoder.
You have to do the same transformation to get the same hash in Perl:
use Digest::SHA qw(hmac_sha1);
use MIME::Base64 qw(decode_base64 encode_base64);
use Encode qw(encode);
$utf16LEPassword = encode("UTF-16LE", $password);
print encode_base64(hmac_sha1($utf16LEPassword, decode_base64($salt)));

How can I replicate this C# hashing in PHP? (toByteArray(), ComputeHash())

I am trying to replicate the following code in PHP, It is example code for an API I have to interface with (The API & Example code is in C#, My app is in PHP 5.3). I'm not a C# developer and so am having trouble doing this.
// C# Code I am trying to replicate in PHP
var apiTokenId = 1887;
var apiToken = "E1024763-1234-5678-91E0-T32E4E7EB316";
// Used to authenticate our request by the API (which is in C#)
var stringToSign = string.Empty;
stringToSign += "POST"+"UserAgent"+"http://api.com/post";
// Here is the issue, How can I do the following 3 lines in PHP?
// No "secret key" provided?.. How do I do this in PHP?
var hmacsha1 = new HMACSHA1(new Guid(apiToken).toByteArray());
// Make a byte array with ASCII encoding.
byte[] byteArray = System.Text.Encoding.ASCII.GetBytes(stringToSign);
// Finally, 'computeHash' of the above (what does this do exactly?!)
var calculatedSignature = Convert.ToBase64String(hmacsha1.ComputeHash(byteArray));
I've tried many variations using pack() and other functions I've found online, but without anything to compare it to, I don't know if i've done it right or not.
Can any C# devs run the above code and post the values generated so I can use that to check/test against?
I've tried checking the MSDN to see what these methods do, but am stuck (and not sure if its correct, as I have nothing to compare it to).
PHP Pseudo Code
// Set vars
$apiToken = 'E1024763-1234-5678-91E0-T32E4E7EB316';
$apiTokenId = '1887';
$stringToSign = "POST"."UserAgent"."http://api.com/post";
// HowTo: Build a `byteArray` of our apiToken? (i think)
// C#: var hmacsha1 = new HMACSHA1(new Guid(apiToken).toByteArray());
// HowTo: Convert our $stringToSign to a ASCII encoded `byteArray`?
// C#: byte[] byteArray = System.Text.Encoding.ASCII.GetBytes(stringToSign);
// HowTo: Generate a base64 string of our (`hmacsha1`.ComputeHash(byteArray))
// C#: var calculatedSignature = Convert.ToBase64String(hmacsha1.ComputeHash(byteArray));
This sounds pretty simple and straightforwaard, but I'm not sure what a few of these C# methods do..
What do these C# methods do/return?
ComputeHash(byteArray) - Computed to what?.. what is returned?
System.Text.Encoding.ASCII.GetBytes(stringToSign); - What does this return?
new HMACSHA1(new Guid(apiToken).toByteArray()); No Secret Key?, what is the key used?
Any resources or help would be much appreciated.
I tried variations of other answers on SO, but no joy.
Can I run the 3 lines of code somewhere online (like JSFiddle but for C#?) so I can see the output of each line?
Update - Bounty Added
Still having trouble with this, I have managed to test the C# code in Visual Studio, but am having trouble getting the same hash generated in PHP.
I would like...
.. the above C# code (specifically, the 3 lines which create the SHA1 hash) to be converted into PHP (Check out the Pseudo Code I posted above). I should be able to match the C# hash using PHP.
If you have any other questions, please ask.
The issue is that the string form of the GUID reverses the order of the 2-character hexadecimal numbers in the first 3 segments of the GUID. For more information see the comments in the example at: http://msdn.microsoft.com/en-us/library/system.guid.tobytearray.aspx
The following code should work:
$apiTokenId = 1887;
$apiToken = "E1024763-1234-5678-91E0-FF2E4E7EB316";
$stringToSign = '';
$hexStr = str_replace('-','',$apiToken);
$c = explode('-',chunk_split($hexStr,2,'-'));
$hexArr = array($c[3],$c[2],$c[1],$c[0],$c[5],$c[4],$c[7],$c[6],$c[8],$c[9],$c[10],$c[11],$c[12],$c[13],$c[14],$c[15]);
$keyStr = '';
for ($i = 0; $i < 16; ++$i) {
$num = hexdec($hexArr[$i]);
$keyStr .= chr($num);
}
$stringToSign .= "POST" . "UserAgent" . "http://api.com/post";
$hmacsha1 = base64_encode(hash_hmac('sha1',$stringToSign,$keyStr,true));
I've tested this code against the C# code you provided above and the output was the same. However, the GUID specified in the original code is not valid so I had to change it slightly.
It's pretty easy, when i don't have to test the code :P
http://php.net/manual/en/function.hash-hmac.php - that's the equivalent of the HMACSHA1 c# class.
string hash_hmac (string $algo , string $data , string $key [, bool $raw_output = false ] )
So $algo = "sha1"
$data is your $stringToSign - since that is already an ascii string (i hope) - the C# was just taking the byte equivalent of the same.
new Guid(apiToken).toByteArray() -> that's a 16 byte (16*8 = 128) representation of the GUID - which is 32*4 = 128 bits. This is the key.
$key is a string so you need the ASCII string equivalent for your $apiToken (which is 32 hex chars - first strip / ignore the dashes in between) - E10247631234567891E0T32E4E7EB316 (correct the key - it cannot have a "T")
function hex2str($hex) {
for($i=0;$i<strlen($hex);$i+=2) $str .= chr(hexdec(substr($hex,$i,2)));
return $str;
}
$hexKey = hex2str($apiToken); //strip the dashes first
http://www.linux-support.com/cms/php-convert-hex-strings-to-ascii-strings/
So the method call now works :
$almostResult = hash_hmac ("sha1" , $stringToSign, $hexKey, true)
This returns a binary string - which you need to convert to base64 encoding.
$final = base64_encode ($almostResult)
That should do it...enjoy :)
I faced almost the same problem and after some googling i found this post:
https://www.reddit.com/r/PHP/comments/2k9tol/string_to_byte_array_using_utf8_encoding/
In PHP strings are already byte arrays. What is the specific problem you are having?
For me the solution was just base64_encode('apikey')

Categories

Resources