【问题标题】:hashcode varbinary(20) c#哈希码 varbinary(20) c#
【发布时间】:2016-03-17 23:00:50
【问题描述】:

我有这门课:

public class SomeClass
{
    public string Str1 { get; set; }
    public string Str2 { get; set; }
    public string Str3 { get; set; }
    public string Str4 { get; set; }
}

我想创建一个哈希键,它作为 varbinary(20) 保存在数据库中,以确定类的唯一性(不区分大小写)。我猜在这种情况下无法使用通常的 GetHashCode 方法。在这种情况下,最佳做法是什么?

【问题讨论】:

  • 我猜在这种情况下不能使用通常的 GetHashCode 方法。 你猜对了 :-) +1 就是为了这个!你不应该坚持 GetHashCode
  • 多么有用的评论(-:
  • 您所说的GetHashCode 是非常重要且非常先进的。我敢肯定,10 个 C# 程序员中有 9 个不知道。
  • 特别是stackoverflow.com/a/10452967/613130的usr的解决方案似乎很漂亮。

标签: c# .net


【解决方案1】:

简单示例:

public class SomeClass
{
    public string Str1 { get; set; }
    public string Str2 { get; set; }
    public string Str3 { get; set; }
    public string Str4 { get; set; }

    public byte[] SHA256()
    {
        using (var sha256 = new SHA256Managed())
        {
            var strings = new[] { Str1, Str2, Str3, Str4 };

            for (int i = 0; i < strings.Length; i++)
            {
                string str = strings[i];

                if (str != null)
                {
                    // Commented lines are for using ToUpperInvariant()
                    //str = str.ToUpperInvariant()
                    byte[] length2 = BitConverter.GetBytes(str.Length);
                    sha256.TransformBlock(length2, 0, length2.Length, length2, 0);

                    // byte[] sortKeyBytes = Encoding.UTF8.GetBytes(str);
                    byte[] sortKeyBytes = CultureInfo.InvariantCulture.CompareInfo.GetSortKey(str, CompareOptions.IgnoreCase).KeyData;

                    sha256.TransformBlock(sortKeyBytes, 0, sortKeyBytes.Length, sortKeyBytes, 0);
                } 
                else
                {
                    byte[] length2 = BitConverter.GetBytes(-1);
                    sha256.TransformBlock(length2, 0, length2.Length, length2, 0);
                }
            }

            sha256.TransformFinalBlock(new byte[0], 0, 0);

            byte[] hash = sha256.Hash;
            return hash;
        }
    }
}

我正在使用 SHA256,该解决方案基于 @usr 在 https://stackoverflow.com/a/10452967/613130 中建议的解决方案。生成的哈希码长 32 个字节,但您可以将其截断为 20(显然您会降低其唯一性)。

我将各种字符串的长度添加到字符串中。这样{ "ABCD", "", "", "" } 将产生与{ "A", "B", "C", "D" } 不同的哈希值。

如果您愿意,您可以使用旧的 ToUpperInvariant() 并基于它进行散列(代码中有一些注释行...您取消注释它们,删除 byte[] sortKeyBytes = CultureInfo.InvariantCulture 并快乐生活:-))。

我不得不说实话,我不确定GetSortKey 的“稳定性”...GetSortKey 在 .NET 10.0 和 Unicode 11.0 中会在 5 年内返回相同的权重吗?谁知道?我当然不会!

MSDN 建议他们可以改变:

如果应用程序对 SortKey 对象进行序列化,当 .NET Framework 有新版本时,应用程序必须重新生成所有排序键。

所以最后我建议基于.ToUpperInvariant() 的替代解决方案(要明确,如果我的老板让我这样做,我会告诉他:使用.ToUpperInvariant())。请注意,即使使用.ToUpperInvariant(),将来也可能会有一些小的变化。可以为现有的小写字符引入新的大写字符。见http://unicode.org/faq/casemap_charprop.html“如果一对已经编码,可以添加一个case对吗?”

【讨论】:

  • 愚蠢的问题。这将返回字节 [32]。这是否意味着我必须在持久化期间使用 varbinary(32)?
  • @csetzkorn 是的,但正如我所写,您可以将其截断为 20 Array.Resize(ref hash, 20)
  • 是的,很抱歉忽略了这一点
  • ...哈希不是只有在完整使用时才唯一吗?
  • @ThorstenDittmar 很明显,通过截断散列可以降低其唯一性。但是,SHA256(或其他哈希)的唯一性更像是一个统计问题......有太多可能的哈希可能会发现冲突。 20个字节还是蛮多的。 MD5 有 16 个字节,多年来一直被认为是安全的。
【解决方案2】:

varbinary(20) 是 160 位,因此您正在寻找 160 位哈希算法。 SHA-1 算法产生一个 160 位的哈希值。

您的问题的目的似乎是创建一个哈希值,该值对于SomeClass 的给定实例来说是唯一的,因此您应该更喜欢快速哈希算法而不是加密哈希算法。 SHA-1 是一种加密算法,但速度非常快,并且在 .NET Framework 中有一个实现。此外,SHA-1 算法存在攻击,因此您不应将其用于加密目的,而应选择 SHA-256 等算法(速度较慢)。

总而言之,我相信 SHA-1 非常适合您的问题。使用该算法很简单。 1)连接字符串,2)将它们转换为大写,3)使用合适的编码(我使用 UTF-8)将它们转换为字节,4)计算哈希:

Byte[] GetHash(SomeClass someClass) {
  if (someClass == null)
    throw new ArgumentNullException("someClass");

  var byteBuffers = GetStrings(someClass).Select(
    s => String.IsNullOrEmpty(s)
         ? new Byte[0] : Encoding.UTF8.GetBytes(s.ToUpperInvariant())
  );
  var bytes = byteBuffers
    .Aggregate(new List<Byte>(), (l, b) => { l.AddRange(b); return l; })
    .ToArray();
  using (var sha1 = new SHA1Managed())
    return sha1.ComputeHash(bytes);
}

IEnumerable<String> GetStrings(SomeClass someClass) {
  yield return someClass.Str1;
  yield return someClass.Str2;
  yield return someClass.Str3;
  yield return someClass.Str4;
}

请注意,任何散列算法(也包括加密算法)都可以并且将会产生冲突。

Xanatos 有一个很好的观点:

我将各种字符串的长度添加到字符串中。这样{ "ABCD", "", "", "" } 将产生与{ "A", "B", "C", "D" } 不同的哈希值。

这是一个替代解决方案,它以稍微不同的方式解决相同的问题,其中每个字符串长度模 256 都包含在哈希中:

Byte[] GetHash(SomeClass someClass) {
  if (someClass == null)
    throw new ArgumentNullException("someClass");

  var byteBuffers = GetBuffers(GetStrings(someClass));
  var bytes = byteBuffers
    .Aggregate(new List<Byte>(), (l, b) => { l.AddRange(b); return l; })
    .ToArray();
  using (var sha1 = new SHA1Managed())
    return sha1.ComputeHash(bytes);
}

IEnumerable<String> GetStrings(SomeClass someClass) {
  yield return someClass.Str1?.ToUpperInvariant();
  yield return someClass.Str2?.ToUpperInvariant();
  yield return someClass.Str3?.ToUpperInvariant();
  yield return someClass.Str4?.ToUpperInvariant();
}

IEnumerable<Byte[]> GetBuffers(IEnumerable<String> strings) {
  foreach (var @string in strings) {
    if (!String.IsNullOrEmpty(@string)) {
      yield return new[] { (Byte) (@string.Length%256) };
      yield return Encoding.UTF8.GetBytes(@string);
    }
    else
      yield return new Byte[1];
  }
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-10-22
    • 2016-01-07
    • 2021-03-15
    • 2012-09-16
    • 1970-01-01
    • 2019-03-26
    • 2016-12-11
    • 2011-02-25
    相关资源
    最近更新 更多