Why do these code blocks produce different results? - c#

Below are 2 similar code blocks. They take a string, encrypt in SHA512, then convert to Base64, I had trouble getting the second code block to produce the same results as my manual test using online calculators and encoders. So I broke the process down step by step and discovered that it was capable of producing the same results as my manual test but only if it behaved like the first code block. Why do these two code blocks produce different results? Thanks!
private void EditText_AfterTextChanged(object sender, AfterTextChangedEventArgs e)
{
//This builds a string to encrypt.
string domain = txtDomain.Text;
string username = txtUsername.Text;
string pin = txtPin.Text;
txtPreview.Text = string.Format("{0}+{1}+{2}", domain, username, pin);
//This takes the above string, encrypts it.
StringBuilder Sb = new StringBuilder();
SHA512Managed HashTool = new SHA512Managed();
Byte[] PhraseAsByte = System.Text.Encoding.UTF8.GetBytes(string.Concat(txtPreview.Text));
Byte[] EncryptedBytes = HashTool.ComputeHash(PhraseAsByte);
HashTool.Clear();
//This rebuilds the calculated hash for manual comparison.
foreach (Byte b in EncryptedBytes)
Sb.Append(b.ToString("x2"));
txtHash.Text = Sb.ToString();
//This takes the rebuilt hash and re-converts it to bytes before encoding it in Base64
EncryptedBytes = System.Text.Encoding.UTF8.GetBytes(string.Concat(txtHash.Text));
txtResult.Text = Convert.ToBase64String(EncryptedBytes);
}
and
private void EditText_AfterTextChanged(object sender, AfterTextChangedEventArgs e)
{
//This builds a string to encrypt.
string domain = txtDomain.Text;
string username = txtUsername.Text;
string pin = txtPin.Text;
txtPreview.Text = string.Format("{0}+{1}+{2}", domain, username, pin);
//This takes the above string, encrypts it.
StringBuilder Sb = new StringBuilder();
SHA512Managed HashTool = new SHA512Managed();
Byte[] PhraseAsByte = System.Text.Encoding.UTF8.GetBytes(string.Concat(txtPreview.Text));
Byte[] EncryptedBytes = HashTool.ComputeHash(PhraseAsByte);
HashTool.Clear();
//This takes the EncryptedBytes and converts them to base64.
txtResult.Text = Convert.ToBase64String(EncryptedBytes);
//This reverses the EncryptedBytes into readable hash for manual comparison
foreach (Byte b in EncryptedBytes)
Sb.Append(b.ToString("x2"));
txtHash.Text = Sb.ToString();
}

Found the answer, no thanks to your less-than-useful downvotes..
Encoding.Unicode is Microsoft's misleading name for UTF-16 (a double-wide encoding, used in the Windows world for historical reasons but not used by anyone else). http://msdn.microsoft.com/en-us/library/system.text.encoding.unicode.aspx
If you inspect your bytes array, you'll see that every second byte is 0x00 (because of the double-wide encoding).
You should be using Encoding.UTF8.GetBytes instead.
But also, you will see different results depending on whether or not you consider the terminating '\0' byte to be part of the data you're hashing. Hashing the two bytes "Hi" will give a different result from hashing the three bytes "Hi". You'll have to decide which you want to do. (Presumably you want to do whichever one your friend's PHP code is doing.)
For ASCII text, Encoding.UTF8 will definitely be suitable. If you're aiming for perfect compatibility with your friend's code, even on non-ASCII inputs, you'd better try a few test cases with non-ASCII characters such as é and 家 and see whether your results still match up. If not, you'll have to figure out what encoding your friend is really using; it might be one of the 8-bit "code pages" that used to be popular before the invention of Unicode. (Again, I think Windows is the main reason that anyone still needs to worry about "code pages".)
Source: Hashing a string with Sha256

Related

C# AES decrypted output is not the same as input

I wanted to do a simple message encrypter to dip my toes into the matter but I can't make it to work. The problem is that whatever input I start with, sometimes it encrypts it but when I try to decrypt it, it just doesn't return the original string. It would be really helpful if you could tell me what I'm doing wrong or guide in the right direction.
Complete code
This are the sections in charge of encrypting and decrypting.
void Decrypt()
{
using var crypt = Aes.Create();
string[] input = ClipboardService.GetText()?.Split(SEPARATOR) ?? Array.Empty<string>();
byte[] key = input[0].ToBytes();
byte[] IV = input[^1].ToBytes();
byte[] value = string.Join(string.Empty, input[1..^1]).ToBytes();
crypt.IV = IV;
crypt.Key = key;
var decryptedValue = crypt.DecryptCbc(value, IV, PaddingMode.Zeros);
string decryptedValueInText = decryptedValue.ToUnicodeString();
ClipboardService.SetText(decryptedValueInText);
LogInfoMessage($"{decryptedValueInText}: {decryptedValue.Length}");
crypt.Clear();
}
void Encrypt()
{
using var crypt = Aes.Create();
crypt.GenerateKey();
string value = ClipboardService.GetText() ?? string.Empty;
var encryptedValue = crypt.EncryptCbc(value.ToBytes(), crypt.IV, PaddingMode.Zeros);
string encryptedValueInText = $"{crypt.Key.ToUnicodeString()}{SEPARATOR}{encryptedValue.ToUnicodeString()}{SEPARATOR}{crypt.IV.ToUnicodeString()}";
ClipboardService.SetText(encryptedValueInText);
LogInfoMessage($"{encryptedValueInText}: {encryptedValue.Length}");
crypt.Clear();
}
There are two extension methods:
public static string ToUnicodeString(this byte[] bytes) => Encoding.Unicode.GetString(bytes);
public static byte[] ToBytes(this string str) => Encoding.Unicode.GetBytes(str);
Example
The input links were:
https://www.youtube.com/
https://www.youtube.com/watch?v=bSA91XTzeuA
I don't think it matters because the key and IV are autogenerated everytime anyways but still.
Per our discussion...
Using the clipboard to store binary data as Unicode text will fail due to invalid UTF-16 codepoints. UTF-16 uses some multi-word encoding for certain Unicode characters, using 32 bits in surrogate pairs to encode Unicode code points from the supplementary planes. There are plenty of primers on the UTF-16 encoding, but basically you have a pair of 16-bit values where the first is in the range 0xD800-0xDBFF and the second must be in the range 0xDC00-0xDFFF. Odds on your encrypted data will break this rule.
As noted, if your encrypted binary data must be sent through a text-only transport you should encode the bytes in the encrypted block using Base64 or similar.
I'd also like to stress that writing methods that can be called with parameters rather than directly accessing the clipboard for I/O makes it much simpler to do testing, including round-trip tests on the various parts of the problem. Proving that the codec is working without reference to the clipboard is a good test and separation of concerns helps to more readily identify the source of problems in the future.

Hashed passwords displays weirdly

I've ran into a curious behaviour when trying to hash a string password and then display the hash in console.
My code is:
static void Main(string[] args)
{
string password = "password";
ConvertPasswordToHash(password);
}
private static void ConvertPasswordToHash(string password)
{
using (HashAlgorithm sha = SHA256.Create())
{
byte[] result = sha.ComputeHash(Encoding.UTF8.GetBytes(password));
string hashText = Encoding.UTF8.GetString(result);
Console.WriteLine(hashText);
StringBuilder sb = new StringBuilder();
foreach (var item in result)
{
sb.Append((char)item);
}
Console.WriteLine(sb);
}
}
The problem is two fold:
The hashTest and sb contain different values (both are 32 characters long before outputting) and 2) Console outputs are even stranger. They are not 32 characters in length and second the outputs are slightly different:
When examining the strings before outputting them, I've noticed that hashText contains for instance \u0004, which could be a unicode character of some sort while sb does not contain that at all (that is before outputting the values into the console).
My questions are:
Which way is the correct way of getting a string of chars from the provided array of bytes?
Why are the console outputs different but only so slightly? It does not look like it is the fault of using the wrong Encoding.
How do I output the correct hash (32 symbols) into the console? Ive tried adding '#' before the strings to cancel any possible carriage returns etc... Pretty much without any result.
Maybe I am missing something obvious. Thank you.
The correct logic should be as follows:
private static void ConvertPasswordToHash(string password)
{
using (HashAlgorithm sha = SHA256.Create())
{
byte[] result = sha.ComputeHash(Encoding.UTF8.GetBytes(password));
StringBuilder sb = new StringBuilder();
foreach (var item in result)
{
sb.Append(item.ToString("x2"));
}
Console.WriteLine(sb);
}
}
ToString("x2") formats the string as two hexadecimal characters.
Live example: https://dotnetfiddle.net/QkREkX
Another way is just to represent your byte[] array as a base 64 string, no StringBuilder required.
byte[] result = sha.ComputeHash(Encoding.UTF8.GetBytes(password));
Console.WriteLine(Convert.ToBase64String(result));

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)));

Authentication from C# to MySQL CakePhp users

I'm currently migrating a CakePhp app to ASP.NET. One thing that is blocking me at this point is that I'm unable to get the right hashing method to get the right password fit so users are able to sign-in from the ASP.NET app.
I have the salt value that is set in config/core.php file.
I've googled to try to determined where to find which hashing algorithm is used, and was not able to find the right query or no result.
here is my C# method so far to hash the password.
public static string ToHash(this string password, string salt)
{
if (string.IsNullOrEmpty(password))
return "";
var provider = new SHA1CryptoServiceProvider();
var encoding = new UnicodeEncoding();
var bytes = provider.ComputeHash(encoding.GetBytes(salt + password));
return Encoding.UTF8.GetString(bytes);
}
I've tried to put the salt before or after the password, it's currently no matching at all, here is the hash password from the cakephp mysql database:
c7fb60ef77dbe3d1681a68e6741ee3a23cc1f41d
Here is what I have from my method
��3[v"���1�:�ѐ��
Not really sure where/how to solve this problem. Any help or hint would be appreciated.
Thanks
I have it!
At least it works for my configuration:
Find salt from Core.php (search for Security.salt in the file). Then, use this code (very similar to the question):
string input = textBox1.Text;
input = "your salt should be here" + input;
var provider = new SHA1CryptoServiceProvider();
var encoding = new UTF8Encoding();
var bytes = provider.ComputeHash(encoding.GetBytes(input));
sha1result.Text = Bin2Hex(bytes);
Helper for Bin2Hex is also here:
public static string Bin2Hex(byte[] ba)
{
string hex = BitConverter.ToString(ba);
return hex.Replace("-", "");
}
It wasn't easy finding it, I searched through some of internet (no results!) and finally resorted to source-digging.
Don't have the Cake sources with me right now, but you should easily be able to look up Cake's hashing and salting method in the source.
The above differences in data look like Cake transforms the hash bytes into a string with the hash's bytes in hex base. Whatever the difference in the hash method, you'll have to convert the C# hash's result into such a string as well before comparing them (or go the other way and parse Cake's hex string and build a byte array out of it).

Hashing non ascii characters C#

Here are two hash generators:
http://www.md5hashgenerator.com/index.php
http://www.miraclesalad.com/webtools/md5.php
Now, my question is:
Why do the hashes differ when trying to hash the char '€' (0x80)?
I assume it happens because '€' is not a normal ASCII character.
Which of the two hashes is 'correct'?
I'm trying to calculate the hash returned by hash generator 1 with C#.
This hashing function doesn't return it.
private string GetMD5Hash(string TextToHash)
{
if ((TextToHash == null) || (TextToHash.Length == 0))
{
return string.Empty;
}
MD5 md5 = new MD5CryptoServiceProvider();
byte[] textToHash = Encoding.Default.GetBytes(TextToHash);
byte[] result = md5.ComputeHash(textToHash);
return BitConverter.ToString(result).Replace("-", "").ToLower();
}
How could I change it so it returns the hash I want?
Additional Info:
I made a little AutoIt script:
#include <Crypt.au3>
ConsoleWrite(StringLower(StringMid(_Crypt_HashData(Chr(128), $CALG_MD5),3)) & #CRLF)
and it returns the hash I want!
However I need a C# code :)
It comes down to which encoding you use to turn the string into a byte[] (hence my suggestion to use try UTF-8, as that is a pretty common choice here; however, any full unicode encoding would work as long as you know which to use) ; for example, based on the string "abc€" we can deduce that the first site might be using any of:
874: Thai (Windows)
936: Chinese Simplified (GB2312)
1250: Central European (Windows)
1252: Western European (Windows)
1253: Greek (Windows)
1254: Turkish (Windows)
1255: Hebrew (Windows)
1256: Arabic (Windows)
1257: Baltic (Windows)
1258: Vietnamese (Windows)
50227: Chinese Simplified (ISO-2022)
51936: Chinese Simplified (EUC)
52936: Chinese Simplified (HZ)
Personally, I'd use UTF-8!
Here's the code I used to find the candidate encodings:
MD5 md5 = new MD5CryptoServiceProvider();
foreach (var enc in Encoding.GetEncodings())
{
byte[] textToHash = enc.GetEncoding().GetBytes("abc€");
byte[] result = md5.ComputeHash(textToHash);
var output = BitConverter.ToString(result).Replace("-", "").ToLower();
if(output == "7a66042043b2cc38ba16a13c596d740e")
{ // result from http://www.md5hashgenerator.com/index.php
Console.WriteLine(enc.CodePage + ": " + enc.DisplayName);
}
}
Further, testing with the string "dnos ʇǝqɐɥdʃɐ" shows that the second site is definitely using UTF-8; the first site finds no matches, so I guess it is using a code-page based encoding, and in short will not work reliably with the full range of unicode.
Both of the MD5 pages you've shown describe MD5 as an operation which works on strings. It isn't - it's an operation which works on byte sequences. In order to convert from a string to a byte sequence, you need to use an encoding.
You've chosen Encoding.Default which is almost always a bad choice - I'd generally choose Encoding.UTF8. However, importantly, neither of those sites say what they're using. However, in real life I would hope you'd either have control over both hashing processes (assuming there really are two) or that any hashing code you don't have control over will specify what encoding to use.
Note that there's a simpler way of creating an instance of MD5 - just use MD5.Create. You should also generally put it in a using statement as it implements IDisposable:
private static string GetMD5Hash(string text)
{
if (string.IsNullOrEmpty(text))
{
return "";
}
using (var md5 = MD5.Create())
{
byte[] hash = md5.ComputeHash(Encoding.UTF8.GetBytes(text));
return BitConverter.ToString(hash).Replace("-", "").ToLower();
}
}

Categories

Resources