I know there are similar questions already on SO but none of them seem to address this problem. I have inherited the following c# code that has been used to create password hashes in a legacy .net app, for various reasons the C# implementation is now being migrated to php:
string input = "fred";
SHA256CryptoServiceProvider provider = new SHA256CryptoServiceProvider();
byte[] hashedValue = provider.ComputeHash(Encoding.ASCII.GetBytes(input));
string output = "";
string asciiString = ASCIIEncoding.ASCII.GetString(hashedValue);
foreach ( char c in asciiString ) {
int tmp = c;
output += String.Format("{0:x2}",
(uint)System.Convert.ToUInt32(tmp.ToString()));
}
return output;
My php code is very simple but for the same input "fred" doesn't produce the same result:
$output = hash('sha256', "fred");
I've traced the problem down to an encoding issue - if I change this line in the C# code:
string asciiString = ASCIIEncoding.ASCII.GetString(hashedValue);
to
string asciiString = ASCIIEncoding.UTF7.GetString(hashedValue);
Then the php and C# output match (it yields d0cfc2e5319b82cdc71a33873e826c93d7ee11363f8ac91c4fa3a2cfcd2286e5).
Since I'm not able to change the .net code I need to work out how to replicate the results in php.
Thanks in advance for any help,
I don’t know PHP well enough to answer your question; however, I must point out that your C# code is broken. Try generating the hash of these two inputs: "âèí" and "çñÿ". You will find that their hash collides:
3f3b221c6c6e3f71223f51695d456d52223f243f3f363949443f3f763b483615
The first bug lies in this operation:
Encoding.ASCII.GetBytes(input)
This assumes that all characters within your input are US-ASCII. Any non-ASCII characters would cause the encoder to fall back to the byte value for the ? character, thereby giving (unwanted) hash collisions, as demonstrated above. Notwithstanding, this will not be an issue if your input is constrained to only allow US-ASCII characters.
The other (more severe) bug lies in the following operation:
ASCIIEncoding.ASCII.GetString(hashedValue)
ASCII only defines mappings for values 0–127. Since the elements of your hashedValue byte array may contain any byte value (0–255), encoding them as ASCII would cause data to be lost whenever a value greater than 127 is encountered. This may lead to further “unwanted” (read: potentially maliciously generated) hash collisions, even when your original input was US-ASCII.
Given that, statistically, half of the bytes constituting your hashes would be greater than 127, then you are losing at least half the strength of your hash algorithm. If a hacker gains access to your stored hashes, it is quite likely that they will manage to devise an attack to generate hash collisions by exploiting this cryptographic weakness.
Edit: Notwithstanding the considerations mentioned in my posts and Jon’s, here is the PHP code that succumbs to the same weakness – so to speak – as your C# code, and thereby gives the same hash:
$output = hash('sha256', $input, true);
for ($i = 0; $i < strlen($output); $i++)
if ($output[$i] > chr(127))
$output[$i] = '?';
$output = bin2hex($output);
Could you use mb_convert_encoding (see http://php.net/manual/en/function.mb-convert-encoding.php - the page also has a link to a list of supported encodings) to convert the PHP string to ASCII from UTF7?
I've traced the problem down to an encoding issue
Yes. You're trying to treat arbitrary binary data as if it's valid text-encoded data. It's not. You should not be using any Encoding here.
If you want the results in hex, the simplest approach is to use BitConverter.ToString
string text = BitConverter.ToString(hashedValue).Replace("-", "").ToLower();
And yes, as pointed out elsewhere, you probably shouldn't be using ASCII to convert the text to binary at the start of the hashing process. I'd probably use UTF-8.
It's really important that you understand the problem here though, as otherwise you'll run into it in other places too. You should only use encodings such as ASCII, UTF-8 etc (on any platform) when you've genuinely got encoded text data. You shouldn't use them for images, the results of cryptography, the results of hashing, etc.
EDIT: Okay, you say you can't change the C# code... it's not clear whether that just means you've got legacy data, or whether you need to keep using the C# code regardless. You should absolutey not run this code for a second longer than you have to.
But in PHP, you may find you can get away with just replacing every byte with a value >= 0x80 in the hash with 0x3F, which is the ASCII for "question mark". If you look through your data you'll probably find there are a lot of 3F bytes in there.
If you can get this to work, I would strongly suggest that you migrate over to the true MD5 hash without losing information like this. Wherever you're storing the hashes, store two: the legacy one (which is all you have now) and the rehashed one. Whenever you're asked to validate that a password is correct, you should:
Check whether you have a "new" one; if so, only use that - ignore the legacy one.
If you only have a legacy one:
Hash the password in the broken way to check whether it's correct
If it is, hash it again properly and store the results in the "new" place.
Then when everyone's logged in correctly once, you'll be able to wipe out the legacy hashes.
Related
I'd like to know if there is a standard code to generate a SHA256 hash using a key. I've come across several types of code, however, they don't generate the same output.
Code found at JokeCamp
private string CreateToken(string message, string secret)
{
secret = secret ?? "";
var encoding = new System.Text.ASCIIEncoding();
byte[] keyByte = encoding.GetBytes(secret);
byte[] messageBytes = encoding.GetBytes(message);
using (var hmacsha256 = new HMACSHA256(keyByte))
{
byte[] hashmessage = hmacsha256.ComputeHash(messageBytes);
return Convert.ToBase64String(hashmessage);
}
}
Here's another one that I found
private static string ComputeHash(string apiKey, string message)
{
var key = Encoding.UTF8.GetBytes(apiKey);
string hashString;
using (var hmac = new HMACSHA256(key))
{
var hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(message));
hashString = Convert.ToBase64String(hash);
}
return hashString;
}
The code generated by both of these are different to what is generated by http://www.freeformatter.com/hmac-generator.html#ad-output
I'll be using the SHA256 for one of our external API's where consumers would hash the data and send it to us. So we just want to make sure we use a standard approach so that they send us the correct hash. Also, I would like to know if there are any well-known nugets for this. I've also tried to find a solution with Bouncy Castle, however, I couldn't find one that uses a key to hash.
The difference is because of the character encodings (ASCII vs UTF-8 in your examples). Note that the hashing algorithm takes an array of bytes, and you do the conversion from a string to that byte-array beforehand.
Your question "whats the standard code" probably hasnt an answer, id say that if you expect the input to contain content from just the ASCII character-space, go for that, if not go for UTF-8. Either way - communicate it to your users
If you want to look at it from a usability perspective and make it the optimal for your users - go for both. Hash the content both ways and check agains the users incoming hash, but it all depends on your evaluation on clock-cycles vs security vs usability (you can have two)
They are almost equivalent.
The difference is how the encoding for the string is established.
In the first portion of code it assumes ASCII, whereas in the second portion it assumes UTF-8. It is possible that the string used another encoding which is none of those.
But regardless of that, the idea is to understand what is the goal of this operation. The truly relevant things in this context are:
Given equal input, output should be the same
There should be no way to retrieve the plaintext only by knowing the output (within a reasonable amount of time)
After hashing, you no longer require the original input in plaintext.
A secure cryptographic hashing function (meaning not older functions like MD5) achieves that.
Then, if your data store where hashes are stored is compromised, the attacker would only have a hash which cannot be used to retrieved the original plaintext. This is why hashing is used rather than encryption, encryption is a reversible operation (through decryption).
Then, within the system, if you've made the decision to use one encoding, you need to keep that decision consistent across all components in your system so they can interoperate.
So, I found a question just like this with an accepted answer, so I hopped off and tried to implement the necessary changes. For some reason though, I am still getting two different strings, and I don't know what it is I'm doing wrong. I tried to comment on the accepted answer to find help, but I lack the reputation to do so. So, I figured I'd ask the question again (that question was 2 years old, too).
Let me explain what I'm doing.
In php...
$intermediatesalt = md5(uniqid(rand(), true));
$salt = substr($intermediatesalt, 0, 8);
$hashpassword = base64_encode(
hash('sha256', $salt . hash('sha256', $password), true)
);
The line that says $hashpassword was taken from the accepted answer from this question. I didn't write any of this php, my friend did. I only know enough about programming to alter the code, but I couldn't create anything in php, let alone HTML.
After the hash has been created, both the hash and the salt are stored on a database.
The C# method I'm using is also from the answer I found here.
public static string ComputeHash(string plainText, string salt)
{
// Convert plain text into a byte array.
byte[] plainTextBytes = Encoding.UTF8.GetBytes(plainText);
byte[] saltBytes = Encoding.UTF8.GetBytes(salt);
SHA256Managed hash = new SHA256Managed();
// Compute hash value of salt.
byte[] plainHash = hash.ComputeHash(plainTextBytes);
byte[] concat = new byte[plainHash.Length + saltBytes.Length];
System.Buffer.BlockCopy(saltBytes, 0, concat, 0, saltBytes.Length);
System.Buffer.BlockCopy(plainHash, 0, concat, saltBytes.Length, plainHash.Length);
byte[] tHashBytes = hash.ComputeHash(concat);
// Convert result into a base64-encoded string.
string hashValue = Convert.ToBase64String(tHashBytes);
// Return the result.
return hashValue;
}
But for some bizarre reason, even though the person who asked the question got what s/he wanted, I am still getting an undesired result.
This is the block of code that loads player data then compares the the php generated hashed password with the c# generated hashed password.
// load the player based on the given email
PlayerStructure.Player newPlayer = MySQL.loadPlayer(email);
// compute a hash based on the given password and the retrieved salt
// then, compare it to the hashed password on the database
string hPassword = Program.ComputeHash(password, newPlayer.salt);
if (newPlayer.password != hPassword)
{
sendStatusMsg(index, "Invalid password.");
sendStatusMsg(index, "1: " + hPassword);
sendStatusMsg(index, "2: " + newPlayer.password);
return;
}
MySQL.loadPlayer loads the hash string and the salt string from the database, and I had to use those sendStatusMessage methods to print strings as this is for a server application that takes up to 15 minutes to load data from the database in debug mode. So I run the debug exe instead, ergo no Console.WriteLine calls. newPlayer.password is the hashed password stored on the database (the password created with php). hPassword is the computed hash using the C# method I borrowed.
The salt is e0071fa9 and the plain-text password is 'test'.
This is the result I get with the sendStatusMsg methods:
Invalid password.
1: 3QQyVEfmBN4kJJHsRQ307TCDYxNMpc4k3r3udBaVz8Y=
2: moHRVv9C0JvpdTk28xGm3uvPPuhatK2rAHXd5he4ZJI=
Any ideas as to what I might be doing incorrectly? As I've stated before, I literally just used the answer on here (borrowing the code almost verbatim) and I'm still not getting my desired result. This is the question I referenced: Why isn't my PHP SHA256 hash equivalent to C# SHA256Managed hash
Because as the answer to question you are linking to says, hash returns a hex-encoded string instead of raw bytes by default. You are passing true as the third parameter to override this behavior for the outer call to hash, but you are not doing the same for the inner call.
In fact why are there two hashes in the first place? The inner hash doesn't seem to serve any purpose at all.
As Jon stated earlier, the php was slightly flawed. If anyone else is attempting to do something like this, know that
$hashpassword = base64_encode(
hash('sha256', $salt . hash('sha256', $password), true)
);
as opposed to
$hashpassword = base64_encode(
hash('sha256', $salt . hash('sha256', $password, true), true)
);
makes a HUGE difference. The second line of php is what did the trick.
I hope this helps!
Please on PHP avoid using your own hashing mechanism, unless you are a security / crypto expert and (more important, know what you are doing).
Have a good look on how password_hash works in PHP (and if using a PHP version that doesn't support it - please upgrade it), you can always use Anthony Ferrara compatibility library for good effect:
https://github.com/ircmaxell/password_compat
If you follow his blog, you will get some hints about the issues at stake:
http://blog.ircmaxell.com/search/label/Security
:)
according to my understanding, a base64 encoded string (ie the output of encode) must always be a multiple of 4.
the c# Convert.FromBase64String says that its input must be a multiple of 4
However if I give it a 25 character string it doesnt complain
[convert]::FromBase64String("ei5gsIELIki+GpnPGyPVBA==")
[convert]::FromBase64String("1ei5gsIELIki+GpnPGyPVBA==")
both work. (The first one is 24 , second is 25)
[convert]::FromBase64String("11ei5gsIELIki+GpnPGyPVBA==")
fails with Invalid length exception
I assume this is a bug in the c# library but I just want to make sure - I am writing code that is sniffing strings to see if they are valid base64 strings and I want to be sure that I understand what a valid one looks like (one possible implementation was to give the string to system.convert and see if it threw - why reinvent perfectly good code)
Yes, this is a flaw (aka bug). It got started due to a perf optimization in an internal helper function named FromBase64_ComputeResultLength() which calculates the length of the byte[] result. It has this comment (edited to fit):
// For legal input, we can assume that 0 <= padding < 3. But it may be
// more for illegal input.
// We will notice it at decode when we see a '=' at the wrong place.
The "we will notice" remark is not entirely accurate, the decoder does flag an '=' if one isn't expected but it fails to check if there's one too many. Which is the case for the 25-char string.
You can report the problem at connect.microsoft.com, I don't see an existing report that resembles it. Do note that it is fairly unlikely that Microsoft can actually fix it any time soon since the change is going to break existing programs that now successfully parse bad base64 strings. It normally requires a major .NET release update to get rid of such problems, like it was done for .NET 4.0, there isn't one on the horizon afaik.
But yes, the simple workaround for you is to check if the string length is divisible by 4, use the % operator.
I am trying to connect my system to a banks payment system. The problem is, their documentation was mostly not correct, if it wasn't a complete disaster.
In the documentation of 3D secure system, the bank asks me to fill out a html form and submit it to their system. The form should include some data AND a SHA1 hash of the data with the data. I tried many times but the bank's system returned "Hash not correct" error all the time.
After some inspection on their example C# code, I found a function they used to get hash results. The problem is function was doing some other stuff to the data rather than just hashing them. And bigger problem is I cannot find out what this piece of code is doing to the string that hashed.
public static string CreateHash(string notHashedStr)
{
SHA1 sha1 = new SHA1CryptoServiceProvider();
byte[] notHashedBytes = System.Text.Encoding.ASCII.GetBytes(notHashedStr);
byte[] hashedByte = sha1.ComputeHash(notHashedBytes);
string hashedStr = System.Convert.ToBase64String(hashedByte);
return hashedStr;
}
I have nearly no experience on .Net framework and also I am on a mac, so I cannot test the code easily, and MSDN is definitely not for me(I am a Ruby developer most of the time, and I know enough C). If anyone can explain what these functions do to the string to be hashed, i'll be very glad.
It's very simple.
Get the ASCII encoded bytes from notHashedStr.
Create a SHA1 hash from that bytes
Convert that hash in a Base64 encoded string.
return that Base64-SHA1-ASCII-String.
I never did any ruby, but it must look a bit like this.
require 'digest/sha1'
returnValue = Digest::SHA1.base64digest 'notHashedStr'
Sir,
I have the jquery solution to encryption on the client side but it create "MD5" only.
I want Salted Md5 Encryption on the Clientside
and Decrypt it at the Server Side in Asp.net 4.0 and C#
My Code for encryption are as follows:
<script type="text/javascript">
function chn() {
var a = document.getElementById('txt1');
var b = document.getElementById('txt2');
var c = a.value; var d = $.md5(c);
b.value = (d);
}
</script>
I want that encryption must be change on every attempt..
Example : first time encryption of abc is xyz
and again if I will try with that name "Abc" then it should create another Encryption
and check on server Side.
Please Help me out
MD5 is a hash, not an encryption mechanism. Hashes are by their very nature lossy, and multiple inputs can (and by virtue of the pigeonhole principle absolutely will) produce the same outputs.
Running MD5 works like counting the number of vowels in a word. If I tell you that a word has 4 vowels in it, and ask you to tell me what the original word was, you simply don't have enough information to give me the the correct answer. You may be able to find some word that has 4 vowels in it, but you won't know whether the word you found was my word. Maybe it is, maybe it isn't. It's mathematically impossible for you to tell.
MD5 works the same way. You're throwing away tons of information, possible gigabytes or terabytes of information, and producing instead a single 16-byte summary.
It is, by intention, an inherently one-way process.
MD5 cant be decrypted. It is a one way hash. Beside I find that anything that could be decrypted on the other end is insecure, in the case it is intercepted. Always design and code to ensure that you can validate a salt and not decrypt it :)