Hashing a string with SHA256
Asked Answered
A

9

181

I try to hash a string using SHA256, I'm using the following code:

using System;
using System.Security.Cryptography;
using System.Text;
 public class Hash
    {
    public static string getHashSha256(string text)
    {
        byte[] bytes = Encoding.Unicode.GetBytes(text);
        SHA256Managed hashstring = new SHA256Managed();
        byte[] hash = hashstring.ComputeHash(bytes);
        string hashString = string.Empty;
        foreach (byte x in hash)
        {
            hashString += String.Format("{0:x2}", x);
        }
        return hashString;
    }
}

However, this code gives significantly different results compared to my friends php, as well as online generators (such as This generator)

Does anyone know what the error is? Different bases?

Anoa answered 13/9, 2012 at 23:32 Comment(2)
Off topic but keep in mind that creating a StringBuilder and using AppendFormat instead of String.Format in your foreach loop will prevent your code from needlessly creating lots of string objects.Annotation
@MarcelLamothe Since the size of the hash is known, it is even better to allocate a char[] with the exact size then create a string from the char[]... I know that your comment is old, in the In the newer frameworks you can also use stackalloc for the Span of char so only a string ovject is created at the end from the spanFawn
C
178

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".)

Chasse answered 13/9, 2012 at 23:50 Comment(12)
This is part of a log-in system, just switching to UTF8.GetBytes() did the trick, thank you :DAnoa
It is to my understanding that PHP uses ASCII by default.Tamboura
ASCII and UTF-8 are for all practical purposes the same, so, good. :) But I've added a paragraph pointing out that they will encode high-byte characters differently, so some tweaking may be required.Chasse
Your comments are incorrect: UTF16 is Unicode. There is nothing misleading about it. Codepages are dead since decades. Windows is no reason to worry about codepages. Since Windows NT Microsoft made big efforts to convert the entire OS to unicode. On the other hand Linux and PHP did the cheap workaround: Instead of using Unicode they still use UTF8. This is not clever. Example: If you want a sorted directory listing in Linux you must convert the UTF8 file names to Unicode, sort them and convert back to UTF8. This is inefficient and awkward. Windows uses wide strings that you can sort directlyOrme
@Elmue, you may be pleased to learn that "sorting by UTF8-encoded bytes" and "sorting by Unicode codepoints" are equivalent! (As is "sorting by UTF16-encoded shorts", but not "sorting by UTF16-encoded bytes" unless you're on a big-endian system, which Windows isn't.) However, "sorting" in Unicode is really a complicated topic that should be saved for another day.Chasse
This is not correct. In UTF16 the greek letter 'beta' 0x3B2 is greater than the greek letter 'alpha' 0x3B1. They can be compared directly. On the other hand in UTF8 you have byte sequences with the length between 1 and 6 bytes. Without decoding UTF8 you do not even know where a letter starts and where it ends. You just have a long snail of bytes. UTF8 MUST first be decoded before you can do anything useful with it.Orme
@Orme don't be so confident in your wrong answers. Try it out; you'll be surprised. Whether the surprise is pleasant or unpleasant is entirely up to you. :)Chasse
Maybe you can do an ultra primitive binary comparison of strings directly in Utf8. But what if you want to do a case insensitive comparison? It is NOT possible. And what if you want a substring(0,8)? It is NOT possible because a character may be between 1 and 6 bytes long. Without decoding Utf8 you don't know where a char starts and where it ends. Any string operation like padding, searching case insensitive, removing or replacing chars, regex needs decoding the Utf8 string. So Microsoft clearly did the better work with UTF16 here than Linux. If this is pleasant or unpleasant is up to you.Orme
@Elmue, “What if you want to do a case insensitive comparison?” You also need to convert bytes in UTF-16 if you want to do this kind of stuff. The fact that it's fixed length does not help one bit.Ctesiphon
The "not used by anyone else" is quite interesting claim, since Java internally handles strings as UTF-16 also...Suicidal
@Orme "Your comments are incorrect: UTF16 is Unicode." You are wrong. "Unicode" is a standard that assigns numbers (code points) to glyphs. Excepting surrogate pairs, it does not state how to represent those numbers as bytes. UTF16 specifies code points <--> bytes. Unicode specifies glyphs <--> code points.Roulers
@Orme UTF-16 does not have fixed-width characters. Most characters are two bytes, but not all - some need 4 bytes to encode, for example, certain rare Chinese/Japanese/Korean characters. UCS-2 is truly fixed-width, but Windows has used UTF-16 since Windows 2000. Also, due to being split over more than one code plane, you might find surprises when sorting Chinese containing the rare characters; even without those characters, which sorting method to you want? Chinese has more than one logical ordering.Outlaw
D
135

I also had this problem with another style of implementation but I forgot where I got it since it was 2 years ago.

static string sha256(string randomString)
{
    var crypt = new SHA256Managed();
    string hash = String.Empty;
    byte[] crypto = crypt.ComputeHash(Encoding.ASCII.GetBytes(randomString));
    foreach (byte theByte in crypto)
    {
        hash += theByte.ToString("x2");
    }
    return hash;
}

When I input something like abcdefghi2013 for some reason it gives different results and results in errors in my login module. Then I tried modifying the code the same way as suggested by Quuxplusone and changed the encoding from ASCII to UTF8 then it finally worked!

static string sha256(string randomString)
{
    var crypt = new System.Security.Cryptography.SHA256Managed();
    var hash = new System.Text.StringBuilder();
    byte[] crypto = crypt.ComputeHash(Encoding.UTF8.GetBytes(randomString));
    foreach (byte theByte in crypto)
    {
        hash.Append(theByte.ToString("x2"));
    }
    return hash.ToString();
}

Thanks again Quuxplusone for the wonderful and detailed answer! :)

Devilry answered 5/2, 2013 at 14:33 Comment(8)
your solution worked for me. but i have different case. it's with sha512 and the code line that solved my problem is hash += bit.ToString("x2"); I've a question here: I was using Convert.ToBase64String(byte[] encryptedBytes) to convert back from bytes to string. that was giving me different result. so what is the different between these two methods of converting from bytes to string..?Brittney
Is it possible to use some customization here (like my own initialization vector) or is appending / prepending random string only option?Lockjaw
I'm not really sure what you mean. This just a very simple hashing function and you can always add/customize it however you want. By appending/prepending random string do you mean salting? Well that's one good way of customizing it for further security.Devilry
It is not recommended to use just SHA hashing without a work factor for storing passwords. In other words, the hashing process of the password needs to be significantly slow, to prevent hackers from guessing fast. Use Bcrypt, or Scrypt for better security.Triploid
@TonSnoei Yes that is true. However, this is some old code from some ancient internal system application back in college that nobody uses anymore and I really wouldn't recommend this myself. Moreover, this thread is specifically about SHA256 encoding and not directly about passwords. Although, I wouldn't mind editing it to remove references to passwords if that tickles your fancy.Devilry
SHA256Managed is IDisposable. I think you should dispose it after use.Sortition
I've found some errors on this answer, but it works after some little fixes. It would be nice to update it and add some additional explanation(s). For example: What's exactly .ToString("x2")Conah
var crypt = new SHA256Managed(); is obsolete. Microsoft is now recommending to use var crypt = SHA256.Create() instead. Also dispose it after useCymene
R
23

New .NET 5+ solution:

If you're using .NET 5 or above, you can now achieve this in just 3 lines of code.

string QuickHash(string input)
{
    var inputBytes = Encoding.UTF8.GetBytes(input);
    var inputHash = SHA256.HashData(inputBytes);
    return Convert.ToHexString(inputHash);
}

string hash = QuickHash("...");

The code above:

  • Uses the new static HashData method on the SHA256 class to avoid instantiating and having to dispose a new instance each time.
  • Uses the new Convert.ToHexString method to convert the hash byte array into a hexadecimal string; eliminating the hassle of using string builders and so on.
  • Uses the new SHA256 class as opposed to the old (now obsolete) SHA256Managed class.
  • Uses UTF-8 encoding to convert the input string into a byte array, which was recommended by the accepted answer.

Note: You should NOT use this method for hashing user passwords. General-purpose hashing functions such as SHA-256 aren't suited for use for passwords anymore, even if you add salts. This is useful for hashing strings that you know have high entropy, such as long randomly generated session tokens and whatnot. For storing passwords, you must look into slower hashing functions that were specifically designed for this purpose, such as Bcrypt, Scrypt, or PBKDF2 (the latter is available natively in .NET — see this)

Ricard answered 26/7, 2022 at 15:40 Comment(2)
Sorry if this is obvious, but in your example code you use "SHA256.ComputeHash(inputBytes)", but then reference "SHA256.HashData" in your explanation. When I use "SHA256.ComputeHash", I get the error "An object reference is required for the non-static field...". When I use "SHA256.HashData" it works. Am I missing something or is that a typo in your code?Tycoon
@Tycoon Oops I'm sorry you're right. That was a typo! SHA256.ComputeHash should be SHA256.HashData instead. I edited my answer and fixed that. Thank you!Ricard
M
15
public static string ComputeSHA256Hash(string text)
{
    using (var sha256 = new SHA256Managed())
    {
        return BitConverter.ToString(sha256.ComputeHash(Encoding.UTF8.GetBytes(text))).Replace("-", "");
    }                
}

The reason why you get different results is because you don't use the same string encoding. The link you put for the on-line web site that computes SHA256 uses UTF8 Encoding, while in your example you used Unicode Encoding. They are two different encodings, so you don't get the same result. With the example above you get the same SHA256 hash of the linked web site. You need to use the same encoding also in PHP.

The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets (No Excuses!)

https://www.joelonsoftware.com/2003/10/08/the-absolute-minimum-every-software-developer-absolutely-positively-must-know-about-unicode-and-character-sets-no-excuses/

Marlite answered 27/11, 2019 at 23:13 Comment(0)
L
11
public string EncryptPassword(string password, string saltorusername)
        {
            using (var sha256 = SHA256.Create())
            {
                var saltedPassword = string.Format("{0}{1}", salt, password);
                byte[] saltedPasswordAsBytes = Encoding.UTF8.GetBytes(saltedPassword);
                return Convert.ToBase64String(sha256.ComputeHash(saltedPasswordAsBytes));
            }
        }
Lucknow answered 23/9, 2019 at 7:0 Comment(2)
i like the fact that you added some salt ^^Pylle
Shouldn't it be called HashPassword() not EncryptPassword()? Encrypting is not the same as hasing.Merino
L
7

The shortest and fastest way ever. Only 1 line!

public static string StringSha256Hash(string text) =>
    string.IsNullOrEmpty(text) ? string.Empty : BitConverter.ToString(new System.Security.Cryptography.SHA256Managed().ComputeHash(System.Text.Encoding.UTF8.GetBytes(text))).Replace("-", string.Empty);
Lundberg answered 18/5, 2020 at 19:26 Comment(2)
I think the condition should be only text == null... since the hash of the empty string should not be an empty string.Fawn
derived cryptographic types are marked as obsolete, starting in .NET 6.Pylle
J
7

I was looking and testing theses answers, and Visual Studio showed me that SHA256Managed is now Obsolete (here)

So, I used the SHA256 class instead:

Encoding enc = Encoding.UTF8;
var hashBuilder = new StringBuilder();
using var hash = SHA256.Create();
byte[] result = hash.ComputeHash(enc.GetBytes(yourStringToHash));
foreach (var b in result)
    hashBuilder.Append(b.ToString("x2"));
string result = hashBuilder.ToString();
Jemimah answered 10/6, 2022 at 18:8 Comment(0)
E
5

In the PHP version you can send 'true' in the last parameter, but the default is 'false'. The following algorithm is equivalent to the default PHP's hash function when passing 'sha256' as the first parameter:

public static string GetSha256FromString(string strData)
    {
        var message = Encoding.ASCII.GetBytes(strData);
        SHA256Managed hashString = new SHA256Managed();
        string hex = "";

        var hashValue = hashString.ComputeHash(message);
        foreach (byte x in hashValue)
        {
            hex += String.Format("{0:x2}", x);
        }
        return hex;
    }
Elyse answered 13/1, 2015 at 11:7 Comment(1)
I wouldn't be using ASCII and do byte[] arrBytes = System.Text.Encoding.UTF8.GetBytes(strData) instead.Inspirit
C
-4

This work for me in .NET Core 3.1.
But not in .NET 5 preview 7.

using System;
using System.Security.Cryptography;
using System.Text;

namespace PortalAplicaciones.Shared.Models
{
    public class Encriptar
    {
        public static string EncriptaPassWord(string Password)
        {
            try
            {
                SHA256Managed hasher = new SHA256Managed();

                byte[] pwdBytes = new UTF8Encoding().GetBytes(Password);
                byte[] keyBytes = hasher.ComputeHash(pwdBytes);

                hasher.Dispose();
                return Convert.ToBase64String(keyBytes);
            }
            catch (Exception ex)
            {
                throw new Exception(ex.Message, ex);
            }
        }  
    }
}
 
Cuprum answered 29/7, 2020 at 11:40 Comment(2)
Welcome to StackOverflow. Please provide some explanation why do you think your proposed solution might solve the OP's problem.Phytohormone
Please try to avoid re-throwing exception. With that you loose the original StackTrace.Phytohormone

© 2022 - 2024 — McMap. All rights reserved.