C# SHA256 Hashing Different To Python Result - c#

I'm writing a little program based off of a Python code that I have found. There is a few lines I need help with. It is about hasing a value using SHA256 encryption.
The python code is as follows:
first = hashlib.sha256((valueOne + valueTwo).encode()).hexdigest()
second = hashlib.sha256(str(timestamp) + value).encode()).hexdigest()
And when I execute it, my values are as follows:
first: 93046e57a3c183186e9e24ebfda7ca04e7eb4d8119060a8a39b48014d4c5172b
second: bde1c946749f6716fde713d46363d90846a841ad56a4cf7eaccbb33aa1eb1b70
My C# code is:
string first = sha256_hash((secret + auth_token));
string second = sha256_hash((timestamp.ToString() + secret));
And when I execute it, my values are:
first: 9346e57a3c183186e9e24ebfda7ca4e7eb4d81196a8a39b48014d4c5172b
second: bde1c946749f6716fde713d46363d9846a841ad56a4cf7eaccbb33aa1eb1b70
As you can see, the values are slightly different. The python code returns two values BOTH with the length of 64 characters, where as in C# the values are 60 characters and 63 characters respectively.
My sha256_hash method is from here: Obtain SHA-256 string of a string
Any help would be appreciated, thanks.

Your hex digest method is not producing length-2 hex values for bytes < 16. The byte \x08 is being added to your hex output as just '8' instead of '08', leading to an output that is too short.
Adjust the format to produce 0-padded hex characters:
foreach (Byte b in result)
Sb.Append(b.ToString("x2"));
See Standard Numeric Format Strings for more information on how to format bytes to hexadecimal strings.

Related

Creating a SHA256 Hash from Ascii Text in C# - equivalent of PHP bin2hex

I need to create a SHA-256 hash in C# from a string which will be passed to a payment service. I have some old sample code that has been provided in PHP and have written a C# version of this - unfortunately the generated hash is not being accepted by the service that requires it so it looks as though I have made a mistake somewhere in my C# code.
The steps the payment service requires to create the hash is:
Collect selected parameters and join into one string
Convert the created string to its ascii hexadecimal representation
Pass the ascii hex representation to the SHA-256 algorithm.
This is the sample PHP code:
$stringToHash = $storeName.$chargetotal.$currency.$sharedsecret; // These are just supplied variables
$ascii = bin2hex($stringToHash);
return hash("sha256", $ascii);
This my C# code:
var hashString = new StringBuilder();
// Append the supplied variables
hashString.Append(storeName);
hashString.Append(chargeTotal.ToString("f2"));
hashString.Append(currency);
hashString.Append(sharedSecret);
var bytes = Encoding.ASCII.GetBytes(hashString.ToString());
using (SHA256 shaM = new SHA256Managed())
{
var hash = shaM.ComputeHash(bytes);
return BitConverter.ToString(hash).Replace("-", "");
}
Specifically, I'm not sure if the method I've used to get the ascii bytes is the same as it what is being done in the PHP bin2hex method.
EDIT - Problem Solved!
Problem solved using Polynomial's solution.
For some background info, the general process this is used in is that payment information is POSTed off to a payment gateway and one part of this is the hash is sent alongside plain text versions of the other variables (apart from the shared secret). There are a lot more variables including a timestamp which would avoid the problem of duplicate hashes.
The gateway server then recomputes the hash to verify the request. For this reason the hashes have to match and I can't change the hashing algorithm or character set etc. For further info this service is from a major global bank...
Unfortunately I don't have any control over the PHP code; it isn't mine. The snippet was sent as part of some 'sample' code and I had to recreate this using C#. For any users struggling with this, the key part was using the string builder instead of the BitConverter.
No, that isn't the same.
The PHP code takes the string, converts it to a hexadecimal representation, then hashes that string. This also involves some intermediate internal steps with conversion to bytes:
Construct the string.
Convert that string to a sequence of bytes, using whichever text encoding (e.g. UTF-8, Windows-1252) is configured to be the default.
Convert that string to the hexadecimal representation of those bytes, as a string. The PHP documentation says "an ASCII string", but this refers only to the fact that basic 0-9 and a-f characters are used. The string's encoding is still whatever PHP's default is.
Convert that string back to bytes, using the default text encoding.
Hash those bytes with SHA256 and return them as a hexadecimal string.
What you've done is convert the input string to a sequence of bytes using ASCII encoding, hash that, then convert that back to a hexadecimal string. This skips the step where the string is first turned into hexadecimal, and also doesn't match the string encoding and will produce different hashes if the inputs contain non-ASCII characters (e.g. Unicode).
The PHP code itself is fragile, since its behaviour is dependent on the system configuration. Two systems with differing locale configurations may produce different hashes due to the inherent differences in underlying character representations. For example, the string "áéíóú€" encodes to c3a1c3a9c3adc3b3c3bae282ac in UTF-8, but e1e9edf3fa3f in ISO-8859-1, and e1e9edf3fa80 in Windows-1252.
If you have control over the PHP code, I strongly recommend altering it to use a single canonical encoding such as UTF-8. For example:
$token = $storeName . $chargetotal . $currency . $sharedsecret;
$utf8token = mb_convert_encoding($token, 'UTF-8');
$hextoken = bin2hex($utf8token);
return hash("sha256", $hextoken);
This removes the encoding ambiguity. Note that using ASCII is a poor idea here - if the store name or any other field included in the input may contain accented, Cyrillic, or CJK characters (you should support internationalisation!) then the hashes you generate will not be properly representative of that name, and may break or collide in unexpected ways.
Another bug is in your numeric conversion. You're telling C# to format the currency with two decimal places, but the PHP side just concatenates the number with the default conversion to string. You should ensure that the currency value on the PHP side is encoded as a number with two decimal places, e.g. 15.00 for 15.
You should also use decimal to store currency values in C#, not float. Floating point numbers are not guaranteed to store currency values accurately due to the way they internally represent numbers. Decimal guarantees that the integer portion of the number will be properly represented. The fractional portion is also stored with more than enough precision to represent currency.
Another thing I'd recommend is using SHA256.Create() instead of explicitly constructing a SHA256Managed object. This will ensure that you utilise the native cryptographic implementation on the platform if it is available, rather than using the slower managed implementation every time.
On the C# side the equivalent is then:
// build the string
var tokenString = new StringBuilder();
tokenString.Append(storeName);
tokenString.Append(chargeTotal.ToString("0.00"));
tokenString.Append(currency);
tokenString.Append(sharedSecret);
// convert to bytes using UTF-8 encoding
var tokenBytes = Encoding.UTF8.GetBytes(tokenString);
// convert those bytes to a hexadecimal string
var tokenBytesHex = BitConverter.ToString(tokenBytes).Replace("-", "");
// convert that string back to bytes (UTF-8 used here since that is the default on PHP, but ASCII will work too)
var tokenBytesHexBytes = Encoding.UTF8.GetBytes(tokenString);
// hash those bytes
using (SHA256 sha256 = SHA256.Create())
{
var hash = sha256.ComputeHash(tokenBytesHexBytes);
return BitConverter.ToString(hash).Replace("-", "");
}
However, this is still broken. When you use BitConverter.ToString to get a hex string from the bytes, the output uses capital letters (e.g. FF for 255). In PHP, bin2hex uses lowercase letters. This matters because it produces a different input to the hash function.
A better solution is to replace those BitConverter calls to more direct hex conversions so you have direct control over the format:
var sb = new StringBuilder();
foreach (byte b in bytes)
sb.AppendFormat("{0:x2}", b);
var hex = sb.ToString();
This should then match on both the PHP and C# side.
As an aside, I strongly recommend rethinking this hashing scheme from a security standpoint.
The first obvious vulnerability is that a store with the same name but with a numeric suffix can produce transaction hash collisions, e.g. a store called Shoe making a $54.00 transaction has the same hash as a store called Shoe5 making a $4.00 transaction, since both will produce Shoe54.00usd. You need to separate the fields using a character that cannot be present in the input strings in order to avoid this. Ideally this involves encoding the fields in a canonical structured format such as BSON or Bencode, but a crude approach here could just be to separate fields with a tab character.
Additionally, building message authentication codes by concatenating secret and non-secret information together is problematic for Merkle-Damgård construction hash functions like MD5, SHA1, and SHA256. The security properties of these hash functions are not tuned for this use-case, and you may fall victim to length extension attacks. Instead, you should consider using a HMAC, which is specifically designed for this use case. You can think of a HMAC like a keyed hash. The only difference is that the shared secret is used as a key, rather than concatenated to the data being hashed.
In PHP you can use hash_hmac for this. In C# you can use HMACSHA256.
Putting that all together, you get:
$token = $storeName . "\t" . $chargetotal . "\t" . $currency;
$utf8token = mb_convert_encoding($token, 'UTF-8');
$hextoken = bin2hex($utf8token);
return hash_hmac("sha256", $hextoken, $sharedsecret);
and
// build the string
var tokenString = new StringBuilder();
tokenString.Append(storeName);
tokenString.Append("\t");
tokenString.Append(chargeTotal.ToString("0.00"));
tokenString.Append("\t");
tokenString.Append(currency);
// convert to bytes using UTF-8 encoding
var tokenBytes = Encoding.UTF8.GetBytes(tokenString);
// convert those bytes to a hexadecimal string
var sb = new StringBuilder();
for (byte b in tokenBytes)
sb.AppendFormat("{0:x2}", b);
var tokenBytesHex = sb.ToString();
// convert that string back to bytes (UTF-8 used here since that is the default on PHP, but ASCII will work too)
var tokenBytesHexBytes = Encoding.UTF8.GetBytes(tokenString);
// hash those bytes using HMAC-SHA256
byte[] sharedSecretBytes = Encoding.UTF8.GetBytes(sharedsecret);
using (HMACSHA256 hmac_sha256 = new HMACSHA256(sharedSecretBytes))
{
var hashBytes = hmac_sha256.ComputeHash(tokenBytesHexBytes);
sb = new StringBuilder();
for (byte b in hashBytes)
sb.AppendFormat("{0:x2}", b);
var hashString = sb.ToString();
return hashString;
}
This still isn't 100% ideal from a security perspective, since two transactions made at different times for the same amount will have the same hash, but you said the fields were examples so I won't demonstrate further there. Suffice to say you should have some unique transaction identifier in there. Another potential issue is that you're leaving strings containing hashes on the heap on the C# side, and those strings are sensitive, but there's not much you can do about that without using SecureString very carefully, and that's quite an involved topic.

c# UTF8 GetString from bytes array not equal to php chr function

I'm trying to make one decoder. Basic system .Net 4.7 I'm trying to migrate this system into php, but I'm having trouble converting bytes. As far as I understand the default string UTF-16le on C#, I understood the ord and chr functions as UCS-2 on the PHP side. I want to do below and I do not get the same result there are codes. What can I do to fix this, thanks in advance
XOR Encoded Text Bytes = [101,107,217,78,40,68,234,218,162,67,139,81,44,166,24,148];
on C#
string result = System.Text.Encoding.UTF8.GetString(destinationArray);
On PHP
for($i=0;$i<sizeof($encoded);$i++){
echo "\t".$encoded[$i]." => ".chr($encoded[$i])."\n";
$tmpStr .= chr($encoded[$i]);
}
C# Result size=26:
ek�N(D�ڢC�Q,��
PHP Result size=16:
ek�N(D�ڢC�Q,��
the strings looks the same, but byte translation is quite different.
C# Result to Bytes array:
byte[] utf8 = System.Text.Encoding.Unicode.GetBytes(result);
Console.WriteLine(string.Join("-", utf8));
response =
101-0-107-0-253-255-78-0-40-0-68-0-253-255-162-6-67-0-253-255-81-0-44-0-253-255-24-0-253-255
PHP Result to Bytes Array:
echo implode("-",unpack("C*", $tmpStr));
response = 101-107-217-78-40-68-234-218-162-67-139-81-44-166-24-148
if php response convert to UTF-16le, results again different
echo implode("-",unpack("C*", mb_convert_encoding($tmpStr,'UTF-16le')));
response =
101-0-107-0-63-0-78-0-40-0-68-0-63-0-162-6-67-0-63-0-81-0-44-0-63-0-24-0-63-0
You are mixing quite different things here.
First, in the C# code, you are not using the same encoding when converting from bytes to a string and then from a string back to bytes: Encoding.UTF8 in the first case and Encoding.Unicode (which is .NET name for UTF-16) in the latter... Things cannot go well if you do this. And by the way, I'm not sure that PHP's UCS2 is equivalent to UTF-16:
UTF-8 encodes characters on 1, 2, 3 or 4 bytes depending on the character
UTF-16 encodes characters on 2 or 4 bytes depending on the character
UCS-2 always encodes characters on 2 bytes, and hence cannot encode more than 65536 characters...
Then what you pass to the 'bytes to string' conversions is not necessarily valid! Because you've XORed the input data (I assume it to be some secret string), the resulting bytes may or may not be a valid sequence in some encodings. For example:
It is not valid in ASCII because you have (in your example) bytes > 127
It is not valid in UTF-8 because 217 followed by 78 is recognized neither as a 1-, 2-, 3-, or 4-byte character by UTF-8; hence, the � you see before the N.
It seems to be invalid UTF-16 as well, but roundtripping works (I could get back the original array using .NET's Unicode.GetString, then Unicode.GetBytes. If I remove your last byte - and end up with an odd number of bytes - then UTF-16 roundtripping does not work any more...
Although I did not test it, it should also be invalid UCS-2 because UCS-2 'looks like' UTF-16 for 2-byte characters.
Roundtripping works with ANSI encodings sucha as windows-1252 because these encodings accept any byte. However, I would discourage using such trick because you have to be sure the same code page is used on both sides of the encoding/decoding process.
Therefore, I think, in your case, the best way to store your XORed bytes into a string would be to convert the array to base64. In C# you can do it this way:
// The code below gives you ZWt1TihEInY+QydRLEIYMA==
var converted = Convert.ToBase64String(array);
// And this one gives you back the initial array
var bytes = Convert.FromBase64String(converted);
Quick googling will tell you to use base64_encode and base64_decode in PHP.
Bottom note: if you want to really understand what's going on with al this encodings stuff, here is the must-read blog post on the subject: https://www.joelonsoftware.com/2003/10/08/the-absolute-minimum-every-software-developer-absolutely-positively-must-know-about-unicode-and-character-sets-no-excuses/

Convert Greek Characters to Terminal Hex Font

I need to Convert greek characters as charmap terminal font hex value.
Example how can i convert
string test="ΞΥΔΙ";
to hex value "\0x8D.......and so on.
If will Convert from String to Hex i'm getting wrong hex value
byte[] ba = Encoding.GetEncoding(1253).GetBytes("ΨΓΣΦ");
var hexString = BitConverter.ToString(ba);
MessageBox.Show(hexString);
Example from character 'Ξ' i'm getting 0xCE
You are close:
Change Code Page from Windows (Win-1253) to MS DOS one (737)
If you want to see codes represented as a string, I suggest using Linq and String.Join
Something like this:
// Terminal uses MS DOS Code Page which is 737 (not Win-1253)
byte[] ba = Encoding.GetEncoding(737).GetBytes("ΞΥΔΙ");
// Let's use Linq to print out a test
var hexString = string.Join(" ", ba.Select(c => $"0x{(int)c:X2}"));
Console.Write(hexString);
Outcome:
0x8D 0x93 0x83 0x88
Please, notice that Ξ has 0x8D code.
Your implementation is actually ok from what I've tested.
I just used the Windows calculator and the Wikipedia 1253 encoding table.
I searched for the 'Ξ' character, and although I'm clueless on Greek characters, a simple search shows that the character indeed matches 0xCE (the font looks funky to me, but the browser seems to like it).

Convert char[] to byte[] results in higher count

Im trying to cast a char-array into a byte-array.
char[] cc = new char[] { ((char)(byte)210) }; // Count = 1
byte[] b = System.Text.Encoding.UTF8.GetBytes(cc); // Count = 2
The conversion results in 2 entries for my byte-array {195, 146}.
I guess theres a problem with the encoding. Any help is appreciated.
After facing some problems I've written this 2 lines for the purpose of testing, so dont mind the style.
Thanks
UTF-8 can use more than just one byte to store a character. It uses only one byte for the ASCII characters within the range from 0-127, other characters need two or more bytes to be stored.
You are encoding the ASCII character 210 which is from the extended ASCII character (numeric value > 127), UTF-8 uses two bytes to store this character.
As M.kazemAkhgary said in the comments above:
cc.Select(c=>(byte)c).ToArray();
The clue was to cast instead of using converting. Thanks for that!

Ruby equivalent to .NET's Encoding.ASCII.GetString(byte[])

Does Ruby have an equivalent to .NET's Encoding.ASCII.GetString(byte[])?
Encoding.ASCII.GetString(bytes[]) takes an array of bytes and returns a string after decoding the bytes using the ASCII encoding.
Assuming your data is in an array like so (each element is a byte, and further, from the description you posted, no larger than 127 in value, that is, a 7-bit ASCII character):
array =[104, 101, 108, 108, 111]
string = array.pack("c*")
After this, string will contain "hello", which is what I believe you're requesting.
The pack method "Packs the contents of arr into a binary sequence according to the directives in the given template string".
"c*" asks the method to interpret each element of the array as a "char". Use "C*" if you want to interpret them as unsigned chars.
http://ruby-doc.org/core/classes/Array.html#M002222
The example given in the documentation page uses the function to convert a string with Unicode characters. In Ruby I believe this is best done using Iconv:
require "iconv"
require "pp"
#Ruby representation of unicode characters is different
unicodeString = "This unicode string contains two characters " +
"with codes outside the ASCII code range, " +
"Pi (\342\x03\xa0) and Sigma (\342\x03\xa3).";
#printing original string
puts unicodeString
i = Iconv.new("ASCII//IGNORE","UTF-8")
#Printing converted string, unicode characters stripped
puts i.iconv(unicodeString)
bytes = i.iconv(unicodeString).unpack("c*")
#printing array of bytes of converted string
pp bytes
Read up on Ruby's Iconv here.
You might also want to check this question.

Categories