175

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?

bluish
  • 26,356
  • 27
  • 122
  • 180
Nattfrosten
  • 1,999
  • 4
  • 16
  • 21
  • 21
    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. – Marcel Lamothe Nov 07 '14 at 13:44
  • 1
    @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 span – zgabi Jan 01 '22 at 15:36

9 Answers9

176

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

Quuxplusone
  • 23,928
  • 8
  • 94
  • 159
  • This is part of a log-in system, just switching to UTF8.GetBytes() did the trick, thank you :D – Nattfrosten Sep 14 '12 at 00:03
  • It is to my understanding that PHP uses ASCII by default. – Cole Tobin Sep 14 '12 at 01:13
  • 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. – Quuxplusone Sep 14 '12 at 01:28
  • 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 directly – Elmue Oct 31 '14 at 22:47
  • 3
    @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 `short`s", 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. – Quuxplusone Nov 02 '14 at 01:17
  • 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. – Elmue Nov 03 '14 at 20:50
  • 2
    @Elmue 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. :) – Quuxplusone Nov 03 '14 at 22:46
  • 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. – Elmue Nov 04 '14 at 04:32
  • 2
    @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. – Arturo Torres Sánchez Dec 23 '14 at 15:13
  • 4
    The "not used by anyone else" is quite interesting claim, since Java internally handles strings as UTF-16 also... – Sami Kuhmonen Mar 20 '15 at 12:52
  • 5
    @Elmue "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. – antiduh Aug 21 '15 at 22:05
  • 2
    @Elmue 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. – Logan Pickup Nov 12 '15 at 01:36
129

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! :)

Bjarki Heiðar
  • 3,117
  • 6
  • 27
  • 40
Nico Dumdum
  • 3,173
  • 2
  • 18
  • 18
  • 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..? – Keval Langalia Mar 11 '15 at 09:53
  • Is it possible to use some customization here (like my own initialization vector) or is appending / prepending random string only option? – FrenkyB Jul 10 '16 at 17:18
  • 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. – Nico Dumdum Jul 15 '16 at 10:23
  • 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. – Ton Snoei Jun 28 '17 at 13:42
  • @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. – Nico Dumdum Jun 30 '17 at 05:23
  • 1
    `SHA256Managed` is `IDisposable`. I think you should dispose it after use. – Jeson Martajaya Dec 25 '19 at 23:32
  • 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")` – carloswm85 Aug 05 '22 at 16:31
  • `var crypt = new SHA256Managed();` is obsolete. Microsoft is now recommending to use `var crypt = SHA256.Create()` instead. Also dispose it after use – Beingnin Dec 30 '22 at 10:46
14
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/

Auto
  • 667
  • 6
  • 9
13

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)

Arad Alvand
  • 8,607
  • 10
  • 51
  • 71
  • 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? – joeshmoe301 Mar 30 '23 at 16:21
  • 1
    @joeshmoe301 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! – Arad Alvand Mar 31 '23 at 17:01
10
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));
            }
        }
ARC
  • 1,061
  • 14
  • 33
6

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);
Erçin Dedeoğlu
  • 4,950
  • 4
  • 49
  • 69
  • 1
    I think the condition should be only text == null... since the hash of the empty string should not be an empty string. – zgabi Jan 01 '22 at 15:42
  • 1
    derived cryptographic types are marked as obsolete, starting in .NET 6. – Fabian Jul 08 '22 at 09:05
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;
    }
Rachel
  • 137
  • 1
  • 3
  • 5
    I wouldn't be using `ASCII` and do `byte[] arrBytes = System.Text.Encoding.UTF8.GetBytes(strData)` instead. – c00000fd Dec 11 '15 at 04:09
5

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();
Vítor Oliveira
  • 1,851
  • 18
  • 23
-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);
            }
        }  
    }
}
 
Peter Csala
  • 17,736
  • 16
  • 35
  • 75