使用SHA256对字符串进行哈希处理

177
我尝试使用SHA256哈希一个字符串,我正在使用以下代码:

我尝试使用SHA256哈希一个字符串,我正在使用以下代码:

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

然而,与我的朋友的PHP以及在线生成器(例如此生成器)相比,这段代码给出了显著不同的结果。

有人知道是什么错误吗?是不同的基数吗?


21
离题了,但请记住,在您的foreach循环中创建一个StringBuilder并使用AppendFormat而不是String.Format将防止您的代码不必要地创建大量字符串对象。 - Marcel Lamothe
1
@MarcelLamothe 既然哈希的大小已知,最好分配一个确切大小的 char[],然后从 char[] 创建一个字符串... 我知道你的评论有点老了,在新的框架中,您还可以使用 stackalloc 来为 char 的 Span 分配内存,因此最终只创建一个字符串对象。 - zgabi
9个回答

177

Encoding.Unicode 是微软对 UTF-16 的一个误导性名称(它是一种双字节编码,在 Windows 世界中由于历史原因而使用,但其他任何人都不使用它)。http://msdn.microsoft.com/en-us/library/system.text.encoding.unicode.aspx

如果检查你的 bytes 数组,你会发现每隔一个字节就有一个 0x00(因为双字节编码)。

你应该使用 Encoding.UTF8.GetBytes

此外,你会看到不同的结果,取决于你是否认为终止的 '\0' 字节是你要哈希的数据的一部分。哈希两个字节的字符串 "Hi" 将产生与哈希三个字节的 "Hi" 不同的结果。你必须决定你想要做哪一个。(假设你想要做与你朋友的 PHP 代码相同的事情。)

对于 ASCII 文本,Encoding.UTF8 绝对适合。如果你想要在非 ASCII 输入上实现与你朋友的代码完全兼容,你最好尝试一些带有非 ASCII 字符(如 é)的测试用例,看看你的结果是否仍然匹配。如果不匹配,你将不得不弄清楚你的朋友实际上使用的编码方式;这可能是在 Unicode 发明之前流行的一种 8 位“代码页”。(同样,我认为 Windows 是任何人仍然需要担心“代码页”的主要原因。)


3
@Elmue,您可能会高兴地了解到,“按UTF8编码字节排序”和“按Unicode代码点排序”是等效的!(按UTF16编码的“short”排序也是如此,但是除非您在大端系统上,否则不要使用“按UTF16编码字节排序”,而Windows不是大端系统。)然而,Unicode中的“排序”实际上是一个复杂的主题,应该留待另一天讨论。 - Quuxplusone
2
@Elmue 不要对你错误的答案那么自信。试一试,你会感到惊讶的。这个惊喜是愉快还是不愉快完全取决于你。 :) - Quuxplusone
4
“不被其他任何人使用”这一说法相当有趣,因为Java内部将字符串处理为UTF-16格式... - Sami Kuhmonen
5
你的评论有误:UTF16是Unicode。你错了。“Unicode”是一种将数字(代码点)分配给字形的标准。除了代理对之外,它不会规定如何将这些数字表示为字节。UTF16指定了代码点<-->字节的转换。Unicode指定了字形<-->代码点的转换。 - antiduh
2
@Elmue UTF-16没有固定宽度的字符。大多数字符是两个字节,但并非全部——例如某些罕见的中日韩字符需要4个字节来编码。UCS-2是真正的固定宽度,但自Windows 2000以来,Windows一直使用UTF-16。此外,由于被分割到多个代码平面上,当排序包含罕见字符的中文时,您可能会发现一些意外情况;即使没有这些字符,您想要哪种排序方法?中文有多种逻辑排序方式。 - Logan Pickup
显示剩余7条评论

133

我之前也遇到过这个问题,当时使用了另一种实现方式,但因为是两年前的事情,所以我忘记了具体来源。

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;
}
当我输入类似于abcdefghi2013的内容时,由于某种原因,我的登录模块会给出不同的结果并导致错误。然后我尝试按照Quuxplusone建议的方式修改代码,并将编码从ASCII改为UTF8,然后它终于工作了!
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();
}

再次感谢 Quuxplusone 提供的精彩详细答案! :)


你的解决方案对我很有效,但我有一个不同的情况。这是关于SHA512的,并且解决了我的问题的代码行是hash += bit.ToString("x2"); 我在这里有一个问题:我之前使用Convert.ToBase64String(byte[] encryptedBytes)将字节数组转换为字符串,但结果不同。那么从字节数组转换为字符串的这两种方法有什么区别呢? - Keval Langalia
我不太确定你的意思。这只是一个非常简单的哈希函数,您可以随时根据需要添加/自定义它。通过附加/前置随机字符串,您是否指盐值?那么这是一种很好的方式,可以进一步定制以提高安全性。 - Nico Dumdum
不建议仅使用SHA哈希算法来存储密码,而没有工作因素。换句话说,密码的哈希处理需要显着减慢,以防止黑客快速猜测。为了获得更好的安全性,请使用Bcrypt或Scrypt。 - Ton Snoei
@TonSnoei 是的,那是真的。然而,这是一些古老的代码来自于大学里的某个过时的内部系统应用,现在已经没有人再使用了,我自己也不建议这样做。此外,这个帖子特别讨论的是SHA256编码,并非直接涉及密码。虽然,如果你喜欢的话,我也不介意编辑掉与密码相关的内容。 - Nico Dumdum
1
SHA256ManagedIDisposable。我认为你应该在使用后将其释放。 - Jeson Martajaya
显示剩余3条评论

17

全新的.NET 5+解决方案:

如果您正在使用.NET 5或更高版本,现在只需三行代码即可轻松实现此功能。

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

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

上面的代码:
  • 使用SHA256类上的新静态HashData方法,避免每次实例化和处理新实例。
  • 使用新的Convert.ToHexString方法将哈希字节数组转换为十六进制字符串;消除了使用字符串构建器等的麻烦。
  • 使用新的SHA256类,而不是旧的(现在已过时的)SHA256Managed类。
  • 使用UTF-8编码将输入字符串转换为字节数组,这是被接受的答案推荐的。

注意:您不应该使用此方法来哈希用户密码。通用哈希函数(如SHA-256)即使添加盐也不适合用于密码,这对于哈希具有高熵的字符串非常有用,例如长随机生成的会话令牌等。对于存储密码,您必须查看特别设计用于此目的的较慢的哈希函数,例如Bcrypt、Scrypt或PBKDF2(后者在.NET中本地可用-请参见this)。


抱歉如果这很明显,但在您的示例代码中,您使用了“SHA256.ComputeHash(inputBytes)”,但在您的解释中引用了“SHA256.HashData”。当我使用“SHA256.ComputeHash”时,我会收到错误消息“需要非静态字段的对象引用...”。当我使用“SHA256.HashData”时它可以工作。我是否遗漏了什么或者这是您代码中的一个错别字? - joeshmoe301
1
@joeshmoe301 对不起,你是正确的。那是个错字! SHA256.ComputeHash 应该改为 SHA256.HashData。我编辑了我的答案并修正了它。谢谢! - aradalvand

14
public static string ComputeSHA256Hash(string text)
{
    using (var sha256 = new SHA256Managed())
    {
        return BitConverter.ToString(sha256.ComputeHash(Encoding.UTF8.GetBytes(text))).Replace("-", "");
    }                
}

你得到不同结果的原因是因为你没有使用相同的字符串编码。你放置在在线网站上计算SHA256的链接使用的是UTF8编码,而在你的示例中使用了Unicode编码。它们是两种不同的编码,所以你得不到相同的结果。使用上面的示例,你可以获得链接网站的相同SHA256哈希值。你需要在PHP中也使用相同的编码。
每位软件开发人员绝对必须了解的有关Unicode和字符集的绝对最低限度(无借口!)

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


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

1
我喜欢你加了一些盐的事实 ^^ - Fabian

7
有史以来最短、最快的方法。只需要一行代码!
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);

1
我认为条件应该只是 text == null... 因为空字符串的哈希值不应该是空字符串。 - zgabi
1
派生的加密类型在 .NET 6 中被标记为过时。 - Fabian

6

我正在查找和测试这些答案,然后 Visual Studio 告诉我 SHA256Managed 现在已经过时 (在此处)

因此,我改用 SHA256 类:

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

5
在PHP版本中,您可以在最后一个参数中发送“true”,但默认值为“false”。当将“sha256”作为第一个参数传递时,以下算法等效于默认PHP的哈希函数:
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;
    }

5
我不会使用 ASCII,而是使用 byte[] arrBytes = System.Text.Encoding.UTF8.GetBytes(strData) - c00000fd

-4

这在 .NET Core 3.1 中对我有效。
但在 .NET 5 预览版 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);
            }
        }  
    }
}
 

2
欢迎来到StackOverflow。请提供一些解释,为什么您认为您提出的解决方案可能会解决OP的问题。 - Peter Csala
5
请尽量避免重新抛出异常。这样会使原始的堆栈跟踪丢失。 - Peter Csala

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接